Polish dashboard and add 12-month body trends
This commit is contained in:
parent
1e1aef86d1
commit
55efbf84bb
11 changed files with 2196 additions and 350 deletions
23
CHANGELOG.md
23
CHANGELOG.md
|
|
@ -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.
|
||||||
|
|
|
||||||
19
docs/reviews/dashboard_12_months.md
Normal file
19
docs/reviews/dashboard_12_months.md
Normal 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.
|
||||||
29
docs/reviews/dashboard_graph_only.md
Normal file
29
docs/reviews/dashboard_graph_only.md
Normal 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.
|
||||||
20
docs/reviews/tui_global_overlay_dashboard_v2.md
Normal file
20
docs/reviews/tui_global_overlay_dashboard_v2.md
Normal 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.
|
||||||
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
|
||||||
41
docs/tui.md
41
docs/tui.md
|
|
@ -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.
|
||||||
|
|
|
||||||
18
tui/include/trainlog/bodyviz.h
Normal file
18
tui/include/trainlog/bodyviz.h
Normal 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
|
||||||
|
|
@ -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
49
tui/src/bodyviz.c
Normal 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;
|
||||||
|
}
|
||||||
2061
tui/src/tui.c
2061
tui/src/tui.c
File diff suppressed because it is too large
Load diff
62
tui/tests/test_bodyviz.c
Normal file
62
tui/tests/test_bodyviz.c
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue