Polish dashboard and add 12-month body trends

This commit is contained in:
fy59 2026-09-05 22:34:12 +02:00
parent 1e1aef86d1
commit 55efbf84bb
11 changed files with 2196 additions and 350 deletions

View file

@ -54,10 +54,25 @@ Implemented:
- left/right asymmetry summaries. - left/right asymmetry summaries.
Next: Next:
- normalized global body overlay graph; - normalized global body overlay graph (implemented);
- metric legend using both colors and symbols; - metric legend using both colors and symbols;
- percentage evolution summary; - percentage evolution summary;
- richer dashboard weight graph; - richer dashboard weight graph (implemented);
- dashboard previous-weight delta and min/max; - dashboard previous-weight delta and min/max (implemented);
- compact body/asymmetry dashboard summary. - compact body/asymmetry dashboard summary (implemented).
<!-- TRAINLOG_GLOBAL_BODY_GRAPH_CHANGELOG _END --> <!-- TRAINLOG_GLOBAL_BODY_GRAPH_CHANGELOG _END -->
### Dashboard graph-only refinement
- removed redundant weight/min/max/asymmetry summary lines;
- dashboard now uses a global body evolution graph;
- dates are shown on X;
- Y is percentage evolution from each metric baseline;
- legend shows metric name, unit, and latest percentage change.
### Dashboard rolling 12 months
- added a fixed 12-month rolling X axis;
- empty months remain visible and empty;
- no zero fill or interpolation across missing months;
- multiple readings in one month use the last monthly value.

View file

@ -0,0 +1,19 @@
# Dashboard rolling 12 months
## Status
```text
DASHBOARD_12_MONTHS=IMPLEMENTED
```
The dashboard always displays the current calendar month plus the previous
11 months.
Empty months remain explicit empty positions on the X axis.
There is no zero filling and no line interpolation across a missing month.
When multiple observations exist in one month, the last observation of that
month is used for the compact dashboard series.
No persistence or Trainlog JSON v1 changes are required.

View file

@ -0,0 +1,29 @@
# Dashboard graph-only layout
## Status
```text
DASHBOARD_GRAPH_ONLY=IMPLEMENTED
```
The dashboard removes redundant body-stat text and uses the available space for
one global body evolution graph.
Graph semantics:
- X axis: recorded dates;
- Y axis: percentage change from each metric's first real value;
- every series keeps a color and a unique symbol;
- legend uses human labels plus units;
- the right side of each legend entry shows current percentage evolution;
- missing body values are never converted to zero.
Examples:
```text
P Poids (kg) -1.8%
T Tour de taille (cm) -4.1%
C Poitrine (cm) +1.7%
```
The four dashboard actions are compressed into one horizontal navigation row.

View file

@ -0,0 +1,20 @@
# Global Body Overlay and Dashboard v2
## Status
```text
TUI_GLOBAL_BODY_OVERLAY=IMPLEMENTED
DASHBOARD_GRAPH_V2=IMPLEMENTED
TRAINLOG_FORMAT_V1=FROZEN
```
`g` in F4 toggles a global body view. Every metric is normalized so its first
real observation equals 100, which makes kg and cm trends comparable without
changing stored values. Series are aligned by timestamp, use distinct symbols,
cycle theme colors, and show `#` on overlapping terminal cells.
The dashboard now shows current weight, previous and total deltas, min/max,
latest waist when available, the largest recent same-observation G/D
difference, and a full-width recent weight graph whose latest point is `O`.
No database migration and no JSON v1 change are required.

View file

@ -95,8 +95,8 @@ TUI_V0_2_POLISH=IMPLEMENTED
TUI_SESSION_DETAILS=IMPLEMENTED TUI_SESSION_DETAILS=IMPLEMENTED
TUI_DURATION_HUMAN_INPUT=IMPLEMENTED TUI_DURATION_HUMAN_INPUT=IMPLEMENTED
TUI_BODY_METRIC_GRAPHS=IMPLEMENTED TUI_BODY_METRIC_GRAPHS=IMPLEMENTED
TUI_GLOBAL_BODY_OVERLAY=NEXT TUI_GLOBAL_BODY_OVERLAY=IMPLEMENTED
DASHBOARD_GRAPH_V2=NEXT DASHBOARD_GRAPH_ONLY=IMPLEMENTED
GATE_2=IN_PROGRESS GATE_2=IN_PROGRESS
TRAINLOG_FORMAT_V1=FROZEN TRAINLOG_FORMAT_V1=FROZEN
``` ```
@ -114,3 +114,7 @@ Next deliverables:
No database schema migration is expected. No database schema migration is expected.
No Trainlog JSON v1 change is expected. No Trainlog JSON v1 change is expected.
<!-- TRAINLOG_GLOBAL_BODY_GRAPH_ROADMAP _END --> <!-- TRAINLOG_GLOBAL_BODY_GRAPH_ROADMAP _END -->
```text
DASHBOARD_12_MONTHS=IMPLEMENTED
```

View file

@ -390,7 +390,7 @@ Immediate order:
<!-- TRAINLOG_TUI_V02_CURRENT_AND_NEXT _END --> <!-- TRAINLOG_TUI_V02_CURRENT_AND_NEXT _END -->
<!-- TRAINLOG_GLOBAL_BODY_GRAPH_NEXT --> <!-- TRAINLOG_GLOBAL_BODY_GRAPH_NEXT -->
## 21. Global body evolution graph — next implementation slice ## 21. Global body evolution graph — implemented
`F4 Corps` keeps its current per-metric graph and gains a global normalized `F4 Corps` keeps its current per-metric graph and gains a global normalized
overlay view. overlay view.
@ -428,7 +428,7 @@ Per-metric navigation remains left/right.
The global view also shows a compact percentage summary from first to latest The global view also shows a compact percentage summary from first to latest
recorded value for each available metric. recorded value for each available metric.
## 22. Dashboard graph v2 — next implementation slice ## 22. Dashboard graph v2 — implemented
The dashboard body-weight graph becomes a richer summary. The dashboard body-weight graph becomes a richer summary.
@ -457,3 +457,40 @@ EXERCISE_PERFORMANCE_GRAPHS=AFTER
Android remains deferred until these TUI daily-use views are satisfactory. Android remains deferred until these TUI daily-use views are satisfactory.
<!-- TRAINLOG_GLOBAL_BODY_GRAPH_NEXT _END --> <!-- TRAINLOG_GLOBAL_BODY_GRAPH_NEXT _END -->
## Dashboard graph-only layout
The home screen avoids duplicating numeric summaries already visible in the
graph.
The dashboard uses:
```text
X = recorded date
Y = percentage evolution from the first real value of each metric
```
The legend identifies every available series using a symbol, theme color,
human metric name, unit (`kg` or `cm`), and current percentage evolution.
Detailed absolute values remain available in `F4 Corps`.
## Dashboard rolling 12-month window
The dashboard graph uses a rolling calendar window ending in the current month.
Exactly 12 month slots are displayed.
Rules:
- months with no observation remain visible and empty;
- missing months are never filled with zero;
- missing months are never interpolated;
- if several observations exist in one month, the last one is used on the
dashboard;
- each metric is normalized from its first visible month in the 12-month
window;
- the detailed F4 history keeps the exact original timestamps and values.
The dashboard legend keeps the last visible raw value and the percentage
change over the visible 12-month window.

View file

@ -0,0 +1,18 @@
#ifndef TRAINLOG_BODYVIZ_H
#define TRAINLOG_BODYVIZ_H
#include "trainlog/status.h"
TrainlogStatus trainlog_body_index100(
double baseline,
double value,
double *output_index
);
TrainlogStatus trainlog_body_percent_change(
double baseline,
double value,
double *output_percent
);
#endif

View file

@ -25,6 +25,7 @@ strict_c_args = [
] ]
trainlog_core_sources = files( trainlog_core_sources = files(
'src/bodyviz.c',
'src/catalog.c', 'src/catalog.c',
'src/database.c', 'src/database.c',
'src/duration.c', 'src/duration.c',
@ -125,3 +126,12 @@ test_body_metrics = executable(
test('duration', test_duration) test('duration', test_duration)
test('body_metrics', test_body_metrics) test('body_metrics', test_body_metrics)
test_bodyviz = executable(
'test_bodyviz',
'tests/test_bodyviz.c',
dependencies: trainlog_core_dep,
c_args: strict_c_args,
)
test('bodyviz', test_bodyviz)

49
tui/src/bodyviz.c Normal file
View file

@ -0,0 +1,49 @@
#include "trainlog/bodyviz.h"
#include <math.h>
#include <stddef.h>
TrainlogStatus trainlog_body_index100(
double baseline,
double value,
double *output_index
)
{
if (output_index == NULL ||
!isfinite(baseline) ||
!isfinite(value) ||
baseline <= 0.0 ||
value <= 0.0) {
return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
*output_index = (value / baseline) * 100.0;
return TRAINLOG_STATUS_OK;
}
TrainlogStatus trainlog_body_percent_change(
double baseline,
double value,
double *output_percent
)
{
double index;
TrainlogStatus status;
if (output_percent == NULL) {
return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
status = trainlog_body_index100(
baseline,
value,
&index
);
if (status != TRAINLOG_STATUS_OK) {
return status;
}
*output_percent = index - 100.0;
return TRAINLOG_STATUS_OK;
}

File diff suppressed because it is too large Load diff

62
tui/tests/test_bodyviz.c Normal file
View file

@ -0,0 +1,62 @@
#include <stdbool.h>
#include <stdio.h>
#include "trainlog/bodyviz.h"
#define CHECK(condition) \
do { \
if (!(condition)) { \
(void)fprintf( \
stderr, \
"CHECK failed at %s:%d: %s\n", \
__FILE__, \
__LINE__, \
#condition \
); \
return false; \
} \
} while (0)
static bool test_bodyviz(void)
{
double value = 0.0;
CHECK(
trainlog_body_index100(
50.0,
55.0,
&value
) == TRAINLOG_STATUS_OK
);
CHECK(value > 109.99);
CHECK(value < 110.01);
CHECK(
trainlog_body_percent_change(
100.0,
96.0,
&value
) == TRAINLOG_STATUS_OK
);
CHECK(value > -4.01);
CHECK(value < -3.99);
CHECK(
trainlog_body_index100(
0.0,
50.0,
&value
) == TRAINLOG_STATUS_INVALID_ARGUMENT
);
return true;
}
int main(void)
{
CHECK(test_bodyviz());
(void)printf("PASS bodyviz\n");
return 0;
}