Skip to content

Commit b269ee3

Browse files
committed
use interpolation for higher accuracy
mainly useful for the overall delta's in the NP and last30day charts however, we may as well display them on the chart as well to make it more consistent, but in this case, we add a small tooltip indicator to avoid confusion
1 parent 29f6c87 commit b269ee3

File tree

3 files changed

+111
-6
lines changed

3 files changed

+111
-6
lines changed

lib/helpers/measurements.dart

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* This file is part of wger Workout Manager <https://github.com/wger-project>.
3+
* Copyright (C) 2020, 2021 wger Team
4+
*
5+
* wger Workout Manager is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
import 'package:wger/helpers/misc.dart';
20+
import 'package:wger/widgets/measurements/charts.dart';
21+
22+
extension MeasurementChartEntryListExtensions on List<MeasurementChartEntry> {
23+
List<MeasurementChartEntry> whereDate(DateTime start, DateTime? end) {
24+
return where((e) => e.date.isAfter(start) && (end == null || e.date.isBefore(end))).toList();
25+
}
26+
27+
List<MeasurementChartEntry> whereDateWithInterpolation(DateTime start, DateTime? end) {
28+
// Make sure our list is sorted by date
29+
sort((a, b) => a.date.compareTo(b.date));
30+
31+
// Initialize result list
32+
final List<MeasurementChartEntry> result = [];
33+
34+
// Check if we have any entries on the same day as start/end
35+
bool hasEntryOnStartDay = false;
36+
bool hasEntryOnEndDay = false;
37+
38+
// Track entries for potential interpolation
39+
MeasurementChartEntry? lastBeforeStart;
40+
MeasurementChartEntry? lastBeforeEnd;
41+
42+
// Single pass through the data
43+
for (final entry in this) {
44+
if (entry.date.isSameDayAs(start)) {
45+
hasEntryOnStartDay = true;
46+
}
47+
if (end != null && entry.date.isSameDayAs(end)) {
48+
hasEntryOnEndDay = true;
49+
}
50+
51+
if (end != null && entry.date.isBefore(end)) {
52+
lastBeforeEnd = entry;
53+
}
54+
55+
if (entry.date.isBefore(start)) {
56+
lastBeforeStart = entry;
57+
} else {
58+
// insert interpolated start value if needed
59+
if (!hasEntryOnStartDay && lastBeforeStart != null) {
60+
result.insert(0, interpolateBetween(lastBeforeStart, entry, start));
61+
hasEntryOnStartDay = true;
62+
}
63+
64+
if (end == null || entry.date.isBefore(end)) {
65+
result.add(entry);
66+
}
67+
if (end != null && entry.date.isAfter(end)) {
68+
// insert interpolated end value if needed
69+
// note: we only interpolate end if we have data going beyond end
70+
// if let's say your plan ends in a week from now, we wouldn't want to fake data until next week.
71+
if (!hasEntryOnEndDay && lastBeforeEnd != null) {
72+
result.add(interpolateBetween(lastBeforeEnd, entry, end));
73+
hasEntryOnEndDay = true;
74+
}
75+
// we added all our values and did all interpolations
76+
// surely all input values from here on are irrelevant.
77+
return result;
78+
}
79+
}
80+
}
81+
return result;
82+
}
83+
}
84+
85+
// caller needs to make sure that before.date < date < after.date
86+
MeasurementChartEntry interpolateBetween(
87+
MeasurementChartEntry before, MeasurementChartEntry after, DateTime date) {
88+
final totalDuration = after.date.difference(before.date).inMilliseconds;
89+
final startDuration = date.difference(before.date).inMilliseconds;
90+
91+
// Create a special DateTime with milliseconds ending in 123 to mark it as interpolated
92+
// which we leverage in the UI
93+
final markedDate =
94+
DateTime(date.year, date.month, date.day, date.hour, date.minute, date.second, 123);
95+
96+
return MeasurementChartEntry(
97+
before.value + (after.value - before.value) * (startDuration / totalDuration),
98+
markedDate,
99+
);
100+
}

lib/widgets/measurements/charts.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,17 @@ class _MeasurementChartWidgetFlState extends State<MeasurementChartWidgetFl> {
7979
getTooltipColor: (touchedSpot) => Theme.of(context).colorScheme.primaryContainer,
8080
getTooltipItems: (touchedSpots) {
8181
return touchedSpots.map((touchedSpot) {
82-
final DateTime date = DateTime.fromMillisecondsSinceEpoch(touchedSpot.x.toInt());
82+
final msSinceEpoch = touchedSpot.x.toInt();
83+
final DateTime date = DateTime.fromMillisecondsSinceEpoch(msSinceEpoch);
8384
final dateStr =
8485
DateFormat.Md(Localizations.localeOf(context).languageCode).format(date);
8586

87+
// Check if this is an interpolated point (milliseconds ending with 123)
88+
final bool isInterpolated = msSinceEpoch % 1000 == 123;
89+
final String interpolatedMarker = isInterpolated ? ' (interpolated)' : '';
90+
8691
return LineTooltipItem(
87-
'$dateStr: ${touchedSpot.y.toStringAsFixed(1)} ${widget._unit}',
92+
'$dateStr: ${touchedSpot.y.toStringAsFixed(1)} ${widget._unit}$interpolatedMarker',
8893
TextStyle(color: touchedSpot.bar.color),
8994
);
9095
}).toList();

lib/widgets/measurements/helpers.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ List<Widget> getOverviewWidgetsSeries(
6161
for (final plan in plans)
6262
...getOverviewWidgets(
6363
AppLocalizations.of(context).chartDuringPlanTitle(name, plan.description),
64-
entriesAll.whereDate(plan.startDate, plan.endDate),
65-
entries7dAvg.whereDate(plan.startDate, plan.endDate),
64+
entriesAll.whereDateWithInterpolation(plan.startDate, plan.endDate),
65+
entries7dAvg.whereDateWithInterpolation(plan.startDate, plan.endDate),
6666
unit,
6767
context,
6868
),
@@ -74,8 +74,8 @@ List<Widget> getOverviewWidgetsSeries(
7474
entriesAll.any((e) => e.date.isAfter(monthAgo)))
7575
...getOverviewWidgets(
7676
AppLocalizations.of(context).chart30DaysTitle(name),
77-
entriesAll.whereDate(monthAgo, null),
78-
entries7dAvg.whereDate(monthAgo, null),
77+
entriesAll.whereDateWithInterpolation(monthAgo, null),
78+
entries7dAvg.whereDateWithInterpolation(monthAgo, null),
7979
unit,
8080
context,
8181
),

0 commit comments

Comments
 (0)