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.
|
||||
|
||||
Next:
|
||||
- normalized global body overlay graph;
|
||||
- normalized global body overlay graph (implemented);
|
||||
- metric legend using both colors and symbols;
|
||||
- percentage evolution summary;
|
||||
- richer dashboard weight graph;
|
||||
- dashboard previous-weight delta and min/max;
|
||||
- compact body/asymmetry dashboard summary.
|
||||
- richer dashboard weight graph (implemented);
|
||||
- dashboard previous-weight delta and min/max (implemented);
|
||||
- compact body/asymmetry dashboard summary (implemented).
|
||||
<!-- 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_DURATION_HUMAN_INPUT=IMPLEMENTED
|
||||
TUI_BODY_METRIC_GRAPHS=IMPLEMENTED
|
||||
TUI_GLOBAL_BODY_OVERLAY=NEXT
|
||||
DASHBOARD_GRAPH_V2=NEXT
|
||||
TUI_GLOBAL_BODY_OVERLAY=IMPLEMENTED
|
||||
DASHBOARD_GRAPH_ONLY=IMPLEMENTED
|
||||
GATE_2=IN_PROGRESS
|
||||
TRAINLOG_FORMAT_V1=FROZEN
|
||||
```
|
||||
|
|
@ -114,3 +114,7 @@ Next deliverables:
|
|||
No database schema migration is expected.
|
||||
No Trainlog JSON v1 change is expected.
|
||||
<!-- 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_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
|
||||
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
|
||||
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.
|
||||
|
||||
|
|
@ -457,3 +457,40 @@ EXERCISE_PERFORMANCE_GRAPHS=AFTER
|
|||
|
||||
Android remains deferred until these TUI daily-use views are satisfactory.
|
||||
<!-- 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(
|
||||
'src/bodyviz.c',
|
||||
'src/catalog.c',
|
||||
'src/database.c',
|
||||
'src/duration.c',
|
||||
|
|
@ -125,3 +126,12 @@ test_body_metrics = executable(
|
|||
|
||||
test('duration', test_duration)
|
||||
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