mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 09:01:55 +01:00
Charts: Determine HR sample gaps dynamically
This commit is contained in:
parent
c0883de546
commit
699e91d8a0
@ -32,7 +32,6 @@ import com.github.mikephil.charting.components.XAxis;
|
||||
import com.github.mikephil.charting.components.YAxis;
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
import com.github.mikephil.charting.data.LineData;
|
||||
import com.github.mikephil.charting.data.LineDataSet;
|
||||
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
|
||||
|
||||
@ -41,7 +40,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -57,6 +55,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ChartUtils;
|
||||
|
||||
|
||||
public class ActivitySummariesChartFragment extends AbstractActivityChartFragment<ChartsData> {
|
||||
@ -267,27 +266,12 @@ public class ActivitySummariesChartFragment extends AbstractActivityChartFragmen
|
||||
tsTranslation = new TimestampTranslation();
|
||||
}
|
||||
|
||||
final List<Entry> heartRateEntries = new ArrayList<>(activityPoints.size());
|
||||
final List<ILineDataSet> heartRateDataSets = new ArrayList<>();
|
||||
int lastTsShorten = 0;
|
||||
for (final ActivityPoint activityPoint : activityPoints) {
|
||||
int tsShorten = tsTranslation.shorten((int) (activityPoint.getTime().getTime() / 1000));
|
||||
if (lastTsShorten == 0 || (tsShorten - lastTsShorten) <= 60 * HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
|
||||
heartRateEntries.add(new Entry(tsShorten, activityPoint.getHeartRate()));
|
||||
} else {
|
||||
if (!heartRateEntries.isEmpty()) {
|
||||
List<Entry> clone = new ArrayList<>(heartRateEntries.size());
|
||||
clone.addAll(heartRateEntries);
|
||||
heartRateDataSets.add(createHeartrateSet(clone, "Heart Rate"));
|
||||
heartRateEntries.clear();
|
||||
}
|
||||
}
|
||||
lastTsShorten = tsShorten;
|
||||
heartRateEntries.add(new Entry(tsShorten, activityPoint.getHeartRate()));
|
||||
}
|
||||
if (!heartRateEntries.isEmpty()) {
|
||||
heartRateDataSets.add(createHeartrateSet(heartRateEntries, "Heart Rate"));
|
||||
}
|
||||
final List<Entry> heartRateEntries = activityPoints.stream()
|
||||
.filter(ap -> HeartRateUtils.getInstance().isValidHeartRateValue(ap.getHeartRate()))
|
||||
.map(ap -> new Entry(tsTranslation.shorten((int) (ap.getTime().getTime() / 1000)), ap.getHeartRate()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final List<ILineDataSet> heartRateDataSets = ChartUtils.findGaps(heartRateEntries, l -> createHeartrateSet(l, "Heart Rate"));
|
||||
|
||||
if (activitySamplesData != null) {
|
||||
// if we have activity samples, replace the heart rate dataset
|
||||
|
@ -35,6 +35,7 @@ import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -46,6 +47,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ChartUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public abstract class AbstractActivityChartFragment<D extends ChartsData> extends AbstractChartFragment<D> {
|
||||
@ -270,33 +272,20 @@ public abstract class AbstractActivityChartFragment<D extends ChartsData> extend
|
||||
}
|
||||
|
||||
boolean hr = supportsHeartrate(gbDevice);
|
||||
final List<Entry> heartRateLineEntries = new ArrayList<>();
|
||||
final List<ILineDataSet> heartRateDataSets = new ArrayList<>();
|
||||
int lastTsShorten = 0;
|
||||
final List<ILineDataSet> heartRateDataSets;
|
||||
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
|
||||
|
||||
// Currently only for HR
|
||||
if (hr) {
|
||||
for (ActivitySample sample : highResSamples) {
|
||||
if (sample.getKind() != ActivityKind.NOT_WORN && heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
|
||||
int tsShorten = tsTranslation.shorten(sample.getTimestamp());
|
||||
if (lastTsShorten == 0 || (tsShorten - lastTsShorten) <= 60 * HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
|
||||
heartRateLineEntries.add(new Entry(tsShorten, sample.getHeartRate()));
|
||||
final List<Entry> heartRateLineEntries = highResSamples.stream()
|
||||
.filter(s -> s.getKind() != ActivityKind.NOT_WORN)
|
||||
.filter(s -> heartRateUtilsInstance.isValidHeartRateValue(s.getHeartRate()))
|
||||
.map(s -> new Entry(tsTranslation.shorten(s.getTimestamp()), s.getHeartRate()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
heartRateDataSets = ChartUtils.findGaps(heartRateLineEntries, l -> createHeartrateSet(l, "Heart Rate"));
|
||||
} else {
|
||||
if (!heartRateLineEntries.isEmpty()) {
|
||||
List<Entry> clone = new ArrayList<>(heartRateLineEntries.size());
|
||||
clone.addAll(heartRateLineEntries);
|
||||
heartRateDataSets.add(createHeartrateSet(clone, "Heart Rate"));
|
||||
heartRateLineEntries.clear();
|
||||
}
|
||||
}
|
||||
lastTsShorten = tsShorten;
|
||||
heartRateLineEntries.add(new Entry(tsShorten, sample.getHeartRate()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!heartRateLineEntries.isEmpty()) {
|
||||
heartRateDataSets.add(createHeartrateSet(heartRateLineEntries, "Heart Rate"));
|
||||
heartRateDataSets = new ArrayList<>();
|
||||
}
|
||||
|
||||
// convert Entry Lists to Datasets
|
||||
|
@ -28,6 +28,7 @@ import java.util.Calendar;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -36,10 +37,10 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Accumulator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ChartUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class HeartRateDailyFragment extends AbstractChartFragment<HeartRateDailyFragment.HeartRateData> {
|
||||
@ -239,36 +240,16 @@ public class HeartRateDailyFragment extends AbstractChartFragment<HeartRateDaily
|
||||
|
||||
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
|
||||
final TimestampTranslation tsTranslation = new TimestampTranslation();
|
||||
final List<Entry> lineEntries = new ArrayList<>();
|
||||
List<? extends ActivitySample> samples = data.samples;
|
||||
final Accumulator accumulator = new Accumulator();
|
||||
|
||||
final List<ILineDataSet> lineDataSets = new ArrayList<>();
|
||||
int lastTsShorten = 0;
|
||||
for (int i =0; i < samples.size(); i++) {
|
||||
final ActivitySample sample = samples.get(i);
|
||||
final int tsShorten = tsTranslation.shorten(sample.getTimestamp());
|
||||
if (!heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
|
||||
continue;
|
||||
}
|
||||
if (lastTsShorten == 0 || (tsShorten - lastTsShorten) <= 60 * HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
|
||||
lineEntries.add(new Entry(tsShorten, sample.getHeartRate()));
|
||||
} else {
|
||||
if (!lineEntries.isEmpty()) {
|
||||
List<Entry> clone = new ArrayList<>(lineEntries.size());
|
||||
clone.addAll(lineEntries);
|
||||
lineDataSets.add(createHeartRateDataSet(clone));
|
||||
lineEntries.clear();
|
||||
}
|
||||
}
|
||||
lastTsShorten = tsShorten;
|
||||
lineEntries.add(new Entry(tsShorten, sample.getHeartRate()));
|
||||
accumulator.add(sample.getHeartRate());
|
||||
}
|
||||
final List<Entry> heartRateLineEntries = samples.stream()
|
||||
.filter(s -> heartRateUtilsInstance.isValidHeartRateValue(s.getHeartRate()))
|
||||
.peek(s -> accumulator.add(s.getHeartRate()))
|
||||
.map(s -> new Entry(tsTranslation.shorten(s.getTimestamp()), s.getHeartRate()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!lineEntries.isEmpty()) {
|
||||
lineDataSets.add(createHeartRateDataSet(lineEntries));
|
||||
}
|
||||
final List<ILineDataSet> lineDataSets = ChartUtils.findGaps(heartRateLineEntries, this::createHeartRateDataSet);
|
||||
|
||||
final int average = accumulator.getCount() > 0 ? (int) Math.round(accumulator.getAverage()) : -1;
|
||||
final int minimum = accumulator.getCount() > 0 ? (int) Math.round(accumulator.getMin()) : -1;
|
||||
|
@ -0,0 +1,57 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public final class ChartUtils {
|
||||
private ChartUtils() {
|
||||
// utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the gaps in a list of values with potentially varying sample rates, and create separate
|
||||
* datasets for each.
|
||||
*/
|
||||
public static List<ILineDataSet> findGaps(final List<Entry> values,
|
||||
final Function<List<Entry>, ILineDataSet> datasetCreator) {
|
||||
if (values.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
} else if (values.size() == 1) {
|
||||
return Collections.singletonList(datasetCreator.apply(values));
|
||||
}
|
||||
|
||||
final List<ILineDataSet> ret = new ArrayList<>();
|
||||
int lastStart = 0;
|
||||
float lastGap = -1;
|
||||
for (int i = 0; i < values.size() - 1; i++) {
|
||||
final float gapToNext = values.get(i + 1).getX() - values.get(i).getX();
|
||||
|
||||
if (lastGap >= 0) {
|
||||
if (gapToNext >= lastGap * 5 && i > lastStart) {
|
||||
// sample rate decreased - insert a gap
|
||||
ret.add(datasetCreator.apply(values.subList(lastStart, i + 1)));
|
||||
lastGap = -1;
|
||||
lastStart = i + 1;
|
||||
continue;
|
||||
} else if (gapToNext < lastGap / 5 && i - 1 > lastStart) {
|
||||
// sample rate increased drastically (workout start?) - insert a gap
|
||||
ret.add(datasetCreator.apply(values.subList(lastStart, i)));
|
||||
lastStart = i;
|
||||
}
|
||||
}
|
||||
|
||||
lastGap = gapToNext;
|
||||
}
|
||||
|
||||
if (lastStart < values.size()) {
|
||||
ret.add(datasetCreator.apply(values.subList(lastStart, values.size())));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user