mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Heart Rate chart: time weighted avarage calculation
This commit is contained in:
+17
-15
@@ -31,8 +31,9 @@ import java.util.Date;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
@@ -41,15 +42,17 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
|
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Accumulator;
|
import nodomain.freeyourgadget.gadgetbridge.util.Accumulator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.TimeWeightedAverageAccumulator;
|
||||||
|
|
||||||
public class HeartRatePeriodFragment extends AbstractChartFragment<HeartRatePeriodFragment.HeartRatePeriodData> {
|
public class HeartRatePeriodFragment extends AbstractChartFragment<HeartRatePeriodFragment.HeartRatePeriodData> {
|
||||||
|
|
||||||
|
protected static final Logger LOG = LoggerFactory.getLogger(HeartRatePeriodFragment.class);
|
||||||
|
|
||||||
static int SEC_PER_DAY = 24 * 60 * 60;
|
static int SEC_PER_DAY = 24 * 60 * 60;
|
||||||
static int DATA_INVALID = -1;
|
static int DATA_INVALID = -1;
|
||||||
|
|
||||||
@@ -95,9 +98,7 @@ public class HeartRatePeriodFragment extends AbstractChartFragment<HeartRatePeri
|
|||||||
View rootView = inflater.inflate(R.layout.fragment_heart_rate, container, false);
|
View rootView = inflater.inflate(R.layout.fragment_heart_rate, container, false);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
|
rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> getChartsHost().enableSwipeRefresh(scrollY == 0));
|
||||||
getChartsHost().enableSwipeRefresh(scrollY == 0);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mDateView = rootView.findViewById(R.id.hr_date_view);
|
mDateView = rootView.findViewById(R.id.hr_date_view);
|
||||||
@@ -141,7 +142,7 @@ public class HeartRatePeriodFragment extends AbstractChartFragment<HeartRatePeri
|
|||||||
DESCRIPTION_COLOR = LEGEND_TEXT_COLOR = TEXT_COLOR = GBApplication.getTextColor(getContext());
|
DESCRIPTION_COLOR = LEGEND_TEXT_COLOR = TEXT_COLOR = GBApplication.getTextColor(getContext());
|
||||||
if (prefs.getBoolean("chart_heartrate_color", false)) {
|
if (prefs.getBoolean("chart_heartrate_color", false)) {
|
||||||
HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_alternative);
|
HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_alternative);
|
||||||
}else{
|
} else {
|
||||||
HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate);
|
HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate);
|
||||||
}
|
}
|
||||||
HEARTRATE_MIN_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_minimum);
|
HEARTRATE_MIN_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_minimum);
|
||||||
@@ -152,6 +153,7 @@ public class HeartRatePeriodFragment extends AbstractChartFragment<HeartRatePeri
|
|||||||
private HeartRateData fetchHeartRateDataForDay(ChartsHost chartsHost, DBHandler db, GBDevice device, int startTs) {
|
private HeartRateData fetchHeartRateDataForDay(ChartsHost chartsHost, DBHandler db, GBDevice device, int startTs) {
|
||||||
int endTs = startTs + SEC_PER_DAY - 1;
|
int endTs = startTs + SEC_PER_DAY - 1;
|
||||||
List<? extends ActivitySample> samples = getActivitySamples(db, device, startTs, endTs);
|
List<? extends ActivitySample> samples = getActivitySamples(db, device, startTs, endTs);
|
||||||
|
final HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
|
||||||
|
|
||||||
int restingHeartRate = DATA_INVALID;
|
int restingHeartRate = DATA_INVALID;
|
||||||
if (supportsHeartRateRestingMeasurement()) {
|
if (supportsHeartRateRestingMeasurement()) {
|
||||||
@@ -163,12 +165,12 @@ public class HeartRatePeriodFragment extends AbstractChartFragment<HeartRatePeri
|
|||||||
.map(HeartRateSample::getHeartRate)
|
.map(HeartRateSample::getHeartRate)
|
||||||
.orElse(DATA_INVALID);
|
.orElse(DATA_INVALID);
|
||||||
}
|
}
|
||||||
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
|
|
||||||
final Accumulator accumulator = new Accumulator();
|
final TimeWeightedAverageAccumulator accumulator = new TimeWeightedAverageAccumulator(60 * HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES, 60);
|
||||||
for (int i = 0; i < samples.size(); i++) {
|
for (int i = 0; i < samples.size(); i++) {
|
||||||
final ActivitySample sample = samples.get(i);
|
final ActivitySample sample = samples.get(i);
|
||||||
if (heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
|
if (heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
|
||||||
accumulator.add(sample.getHeartRate());
|
accumulator.add(sample.getTimestamp(), sample.getHeartRate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +186,7 @@ public class HeartRatePeriodFragment extends AbstractChartFragment<HeartRatePeri
|
|||||||
Pair<Integer, Integer> startAndEndTs = getStartAndEndTS();
|
Pair<Integer, Integer> startAndEndTs = getStartAndEndTS();
|
||||||
final int startTs = startAndEndTs.getKey();
|
final int startTs = startAndEndTs.getKey();
|
||||||
|
|
||||||
List<HeartRateData> result = new ArrayList<HeartRateData>();
|
List<HeartRateData> result = new ArrayList<>();
|
||||||
for (int i = 0; i < TOTAL_DAYS; i++) {
|
for (int i = 0; i < TOTAL_DAYS; i++) {
|
||||||
HeartRateData dayData = fetchHeartRateDataForDay(chartsHost, db, device, startTs + i * SEC_PER_DAY);
|
HeartRateData dayData = fetchHeartRateDataForDay(chartsHost, db, device, startTs + i * SEC_PER_DAY);
|
||||||
result.add(dayData);
|
result.add(dayData);
|
||||||
@@ -215,8 +217,8 @@ public class HeartRatePeriodFragment extends AbstractChartFragment<HeartRatePeri
|
|||||||
YAxis yAxisRight = hrLineChart.getAxisRight();
|
YAxis yAxisRight = hrLineChart.getAxisRight();
|
||||||
yAxisRight.setDrawLabels(true);
|
yAxisRight.setDrawLabels(true);
|
||||||
|
|
||||||
YAxis[] yAxixArr = {yAxisLeft, yAxisRight};
|
YAxis[] yAxisArr = {yAxisLeft, yAxisRight};
|
||||||
for (YAxis y : yAxixArr) {
|
for (YAxis y : yAxisArr) {
|
||||||
y.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
|
y.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
|
||||||
y.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
|
y.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
|
||||||
y.setDrawGridLines(false);
|
y.setDrawGridLines(false);
|
||||||
@@ -318,7 +320,7 @@ public class HeartRatePeriodFragment extends AbstractChartFragment<HeartRatePeri
|
|||||||
final int startTs = startAndEndTs.getKey();
|
final int startTs = startAndEndTs.getKey();
|
||||||
final int endTs = startAndEndTs.getValue();
|
final int endTs = startAndEndTs.getValue();
|
||||||
|
|
||||||
Date date = new Date((long) endTs * 1000);
|
//Date date = new Date((long) endTs * 1000);
|
||||||
mDateView.setText(DateTimeUtils.formatDaysUntil(TOTAL_DAYS, getTSEnd()));
|
mDateView.setText(DateTimeUtils.formatDaysUntil(TOTAL_DAYS, getTSEnd()));
|
||||||
final XAxis x = hrLineChart.getXAxis();
|
final XAxis x = hrLineChart.getXAxis();
|
||||||
if (TOTAL_DAYS == 1) {
|
if (TOTAL_DAYS == 1) {
|
||||||
@@ -430,7 +432,7 @@ public class HeartRatePeriodFragment extends AbstractChartFragment<HeartRatePeri
|
|||||||
ValueFormatter formatter = new ValueFormatter() {
|
ValueFormatter formatter = new ValueFormatter() {
|
||||||
@Override
|
@Override
|
||||||
public String getFormattedValue(float value) {
|
public String getFormattedValue(float value) {
|
||||||
int ts = startTs + SEC_PER_DAY * (int)value;
|
int ts = startTs + SEC_PER_DAY * (int) value;
|
||||||
return formatDay.format(new Date(ts * 1000L));
|
return formatDay.format(new Date(ts * 1000L));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -442,7 +444,7 @@ public class HeartRatePeriodFragment extends AbstractChartFragment<HeartRatePeri
|
|||||||
final int restingAvg = restingAccumulator.getCount() > 0 ? (int) Math.round(restingAccumulator.getAverage()) : DATA_INVALID;
|
final int restingAvg = restingAccumulator.getCount() > 0 ? (int) Math.round(restingAccumulator.getAverage()) : DATA_INVALID;
|
||||||
setStatistics(average, minimum, maximum, restingAvg);
|
setStatistics(average, minimum, maximum, restingAvg);
|
||||||
|
|
||||||
List<ILineDataSet> dataSets = new ArrayList<ILineDataSet>();
|
List<ILineDataSet> dataSets = new ArrayList<>();
|
||||||
if (GBApplication.getPrefs().getBoolean("charts_show_average", true)) {
|
if (GBApplication.getPrefs().getBoolean("charts_show_average", true)) {
|
||||||
dataSets.add(createHeartRateDataSet(avgLineData, HEARTRATE_COLOR));
|
dataSets.add(createHeartRateDataSet(avgLineData, HEARTRATE_COLOR));
|
||||||
}
|
}
|
||||||
|
|||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||||
|
|
||||||
|
public class TimeWeightedAverageAccumulator {
|
||||||
|
private long lastTimestamp;
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
private double min = Double.MAX_VALUE;
|
||||||
|
private double max = -Double.MAX_VALUE;
|
||||||
|
|
||||||
|
private double weightedSum = 0.0;
|
||||||
|
private long totalWeight = 0;
|
||||||
|
private boolean first = true;
|
||||||
|
|
||||||
|
private final long defaultTimeDiff;
|
||||||
|
private final long maxTimeDiff;
|
||||||
|
|
||||||
|
public TimeWeightedAverageAccumulator(long maxTimeDiff, long defaultTimeDiff) {
|
||||||
|
this.maxTimeDiff = maxTimeDiff;
|
||||||
|
this.defaultTimeDiff = defaultTimeDiff;
|
||||||
|
this.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(long timestamp, double value) {
|
||||||
|
if (value > max) {
|
||||||
|
max = value;
|
||||||
|
}
|
||||||
|
if (value < min) {
|
||||||
|
min = value;
|
||||||
|
}
|
||||||
|
if (first) {
|
||||||
|
if (defaultTimeDiff > 0) {
|
||||||
|
weightedSum = value * defaultTimeDiff;
|
||||||
|
totalWeight = defaultTimeDiff;
|
||||||
|
} else {
|
||||||
|
weightedSum = 0;
|
||||||
|
totalWeight = 0;
|
||||||
|
}
|
||||||
|
lastTimestamp = timestamp;
|
||||||
|
first = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long timeDiff = timestamp - lastTimestamp;
|
||||||
|
if (maxTimeDiff > 0 && defaultTimeDiff > 0 && timeDiff > maxTimeDiff) {
|
||||||
|
timeDiff = defaultTimeDiff;
|
||||||
|
}
|
||||||
|
weightedSum += value * timeDiff;
|
||||||
|
totalWeight += timeDiff;
|
||||||
|
lastTimestamp = timestamp;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getAverage() {
|
||||||
|
return totalWeight == 0 ? 0.0 : weightedSum / totalWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getMin() {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getMax() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user