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.components.YAxis;
|
||||||
import com.github.mikephil.charting.data.Entry;
|
import com.github.mikephil.charting.data.Entry;
|
||||||
import com.github.mikephil.charting.data.LineData;
|
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.formatter.ValueFormatter;
|
||||||
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
|
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
|
||||||
|
|
||||||
@ -41,7 +40,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
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.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.ChartUtils;
|
||||||
|
|
||||||
|
|
||||||
public class ActivitySummariesChartFragment extends AbstractActivityChartFragment<ChartsData> {
|
public class ActivitySummariesChartFragment extends AbstractActivityChartFragment<ChartsData> {
|
||||||
@ -267,27 +266,12 @@ public class ActivitySummariesChartFragment extends AbstractActivityChartFragmen
|
|||||||
tsTranslation = new TimestampTranslation();
|
tsTranslation = new TimestampTranslation();
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Entry> heartRateEntries = new ArrayList<>(activityPoints.size());
|
final List<Entry> heartRateEntries = activityPoints.stream()
|
||||||
final List<ILineDataSet> heartRateDataSets = new ArrayList<>();
|
.filter(ap -> HeartRateUtils.getInstance().isValidHeartRateValue(ap.getHeartRate()))
|
||||||
int lastTsShorten = 0;
|
.map(ap -> new Entry(tsTranslation.shorten((int) (ap.getTime().getTime() / 1000)), ap.getHeartRate()))
|
||||||
for (final ActivityPoint activityPoint : activityPoints) {
|
.collect(Collectors.toList());
|
||||||
int tsShorten = tsTranslation.shorten((int) (activityPoint.getTime().getTime() / 1000));
|
|
||||||
if (lastTsShorten == 0 || (tsShorten - lastTsShorten) <= 60 * HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
|
final List<ILineDataSet> heartRateDataSets = ChartUtils.findGaps(heartRateEntries, l -> createHeartrateSet(l, "Heart Rate"));
|
||||||
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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activitySamplesData != null) {
|
if (activitySamplesData != null) {
|
||||||
// if we have activity samples, replace the heart rate dataset
|
// 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.Calendar;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
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.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.ChartUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
|
||||||
public abstract class AbstractActivityChartFragment<D extends ChartsData> extends AbstractChartFragment<D> {
|
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);
|
boolean hr = supportsHeartrate(gbDevice);
|
||||||
final List<Entry> heartRateLineEntries = new ArrayList<>();
|
final List<ILineDataSet> heartRateDataSets;
|
||||||
final List<ILineDataSet> heartRateDataSets = new ArrayList<>();
|
|
||||||
int lastTsShorten = 0;
|
|
||||||
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
|
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
|
||||||
|
|
||||||
// Currently only for HR
|
// Currently only for HR
|
||||||
if (hr) {
|
if (hr) {
|
||||||
for (ActivitySample sample : highResSamples) {
|
final List<Entry> heartRateLineEntries = highResSamples.stream()
|
||||||
if (sample.getKind() != ActivityKind.NOT_WORN && heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
|
.filter(s -> s.getKind() != ActivityKind.NOT_WORN)
|
||||||
int tsShorten = tsTranslation.shorten(sample.getTimestamp());
|
.filter(s -> heartRateUtilsInstance.isValidHeartRateValue(s.getHeartRate()))
|
||||||
if (lastTsShorten == 0 || (tsShorten - lastTsShorten) <= 60 * HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
|
.map(s -> new Entry(tsTranslation.shorten(s.getTimestamp()), s.getHeartRate()))
|
||||||
heartRateLineEntries.add(new Entry(tsShorten, sample.getHeartRate()));
|
.collect(Collectors.toList());
|
||||||
} else {
|
|
||||||
if (!heartRateLineEntries.isEmpty()) {
|
heartRateDataSets = ChartUtils.findGaps(heartRateLineEntries, l -> createHeartrateSet(l, "Heart Rate"));
|
||||||
List<Entry> clone = new ArrayList<>(heartRateLineEntries.size());
|
} else {
|
||||||
clone.addAll(heartRateLineEntries);
|
heartRateDataSets = new ArrayList<>();
|
||||||
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"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert Entry Lists to Datasets
|
// convert Entry Lists to Datasets
|
||||||
|
@ -28,6 +28,7 @@ import java.util.Calendar;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
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.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.ChartUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
|
||||||
public class HeartRateDailyFragment extends AbstractChartFragment<HeartRateDailyFragment.HeartRateData> {
|
public class HeartRateDailyFragment extends AbstractChartFragment<HeartRateDailyFragment.HeartRateData> {
|
||||||
@ -239,36 +240,16 @@ public class HeartRateDailyFragment extends AbstractChartFragment<HeartRateDaily
|
|||||||
|
|
||||||
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
|
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
|
||||||
final TimestampTranslation tsTranslation = new TimestampTranslation();
|
final TimestampTranslation tsTranslation = new TimestampTranslation();
|
||||||
final List<Entry> lineEntries = new ArrayList<>();
|
|
||||||
List<? extends ActivitySample> samples = data.samples;
|
List<? extends ActivitySample> samples = data.samples;
|
||||||
final Accumulator accumulator = new Accumulator();
|
final Accumulator accumulator = new Accumulator();
|
||||||
|
|
||||||
final List<ILineDataSet> lineDataSets = new ArrayList<>();
|
final List<Entry> heartRateLineEntries = samples.stream()
|
||||||
int lastTsShorten = 0;
|
.filter(s -> heartRateUtilsInstance.isValidHeartRateValue(s.getHeartRate()))
|
||||||
for (int i =0; i < samples.size(); i++) {
|
.peek(s -> accumulator.add(s.getHeartRate()))
|
||||||
final ActivitySample sample = samples.get(i);
|
.map(s -> new Entry(tsTranslation.shorten(s.getTimestamp()), s.getHeartRate()))
|
||||||
final int tsShorten = tsTranslation.shorten(sample.getTimestamp());
|
.collect(Collectors.toList());
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lineEntries.isEmpty()) {
|
final List<ILineDataSet> lineDataSets = ChartUtils.findGaps(heartRateLineEntries, this::createHeartRateDataSet);
|
||||||
lineDataSets.add(createHeartRateDataSet(lineEntries));
|
|
||||||
}
|
|
||||||
|
|
||||||
final int average = accumulator.getCount() > 0 ? (int) Math.round(accumulator.getAverage()) : -1;
|
final int average = accumulator.getCount() > 0 ? (int) Math.round(accumulator.getAverage()) : -1;
|
||||||
final int minimum = accumulator.getCount() > 0 ? (int) Math.round(accumulator.getMin()) : -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