Refactor ChartView/ChartFragment to use ChartPoint.

This commit is contained in:
Dennis Guse
2020-04-03 23:48:34 +02:00
parent 38a15a96b3
commit 208f1d71ba
8 changed files with 375 additions and 264 deletions
@@ -15,6 +15,7 @@
*/ */
package de.dennisguse.opentracks.chart; package de.dennisguse.opentracks.chart;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -44,7 +45,17 @@ public class ChartValueSeriesTest {
R.string.description_elevation_metric, R.string.description_elevation_metric,
R.string.description_elevation_imperial, R.string.description_elevation_imperial,
R.color.chart_elevation_fill, R.color.chart_elevation_fill,
R.color.chart_elevation_border); R.color.chart_elevation_border) {
@Override
double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getElevation();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return false;
}
};
} }
@Test @Test
@@ -63,8 +74,8 @@ public class ChartValueSeriesTest {
@Test @Test
public void testSmallUpdates() { public void testSmallUpdates() {
series.update(0); series.update(new ChartPoint(0));
series.update(10); series.update(new ChartPoint(10));
series.updateDimension(); series.updateDimension();
Assert.assertEquals(100, series.getInterval()); Assert.assertEquals(100, series.getInterval());
Assert.assertEquals(0, series.getMinMarkerValue()); Assert.assertEquals(0, series.getMinMarkerValue());
@@ -73,8 +84,8 @@ public class ChartValueSeriesTest {
@Test @Test
public void testBigUpdates() { public void testBigUpdates() {
series.update(0); series.update(new ChartPoint(0));
series.update(901); series.update(new ChartPoint(901));
series.updateDimension(); series.updateDimension();
Assert.assertEquals(1000, series.getInterval()); Assert.assertEquals(1000, series.getInterval());
Assert.assertEquals(0, series.getMinMarkerValue()); Assert.assertEquals(0, series.getMinMarkerValue());
@@ -83,8 +94,8 @@ public class ChartValueSeriesTest {
@Test @Test
public void testNotZeroBasedUpdates() { public void testNotZeroBasedUpdates() {
series.update(220); series.update(new ChartPoint(220));
series.update(250); series.update(new ChartPoint(250));
series.updateDimension(); series.updateDimension();
Assert.assertEquals(100, series.getInterval()); Assert.assertEquals(100, series.getInterval());
Assert.assertEquals(200, series.getMinMarkerValue()); Assert.assertEquals(200, series.getMinMarkerValue());
@@ -30,6 +30,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import de.dennisguse.opentracks.TrackStubUtils; import de.dennisguse.opentracks.TrackStubUtils;
import de.dennisguse.opentracks.chart.ChartPoint;
import de.dennisguse.opentracks.chart.ChartView; import de.dennisguse.opentracks.chart.ChartView;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
@@ -74,10 +75,10 @@ public class ChartFragmentTest {
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
// No input. // No input.
double[] point = chartFragment.createPendingPoint(trackPoint); ChartPoint point = chartFragment.createPendingPoint(trackPoint);
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01); Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.CADENCE_SERIES + 1], 0.01); Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.POWER_SERIES + 1], 0.01); Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
// Input incorrect state. // Input incorrect state.
// Creates SensorData. // Creates SensorData.
@@ -85,9 +86,9 @@ public class ChartFragmentTest {
trackPoint.setSensorDataSet(sensorDataSet); trackPoint.setSensorDataSet(sensorDataSet);
// Test. // Test.
point = chartFragment.createPendingPoint(trackPoint); point = chartFragment.createPendingPoint(trackPoint);
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01); Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.CADENCE_SERIES + 1], 0.01); Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.POWER_SERIES + 1], 0.01); Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
} }
/** /**
@@ -97,10 +98,10 @@ public class ChartFragmentTest {
public void testCreatePendingPoint_sensorCorrect() { public void testCreatePendingPoint_sensorCorrect() {
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
// No input. // No input.
double[] point = chartFragment.createPendingPoint(trackPoint); ChartPoint point = chartFragment.createPendingPoint(trackPoint);
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01); Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.CADENCE_SERIES + 1], 0.01); Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.POWER_SERIES + 1], 0.01); Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
// Creates SensorData. // Creates SensorData.
SensorDataSet sensorDataSet = new SensorDataSet(100, 101, 102); SensorDataSet sensorDataSet = new SensorDataSet(100, 101, 102);
@@ -109,9 +110,9 @@ public class ChartFragmentTest {
trackPoint.setSensorDataSet(sensorDataSet); trackPoint.setSensorDataSet(sensorDataSet);
// Test. // Test.
point = chartFragment.createPendingPoint(trackPoint); point = chartFragment.createPendingPoint(trackPoint);
Assert.assertEquals(100.0, point[ChartView.HEART_RATE_SERIES + 1], 0.01); Assert.assertEquals(100.0, point.getHeartRate(), 0.01);
Assert.assertEquals(101.0, point[ChartView.CADENCE_SERIES + 1], 0.01); Assert.assertEquals(101.0, point.getCadence(), 0.01);
Assert.assertEquals(102.0, point[ChartView.POWER_SERIES + 1], 0.01); Assert.assertEquals(102.0, point.getPower(), 0.01);
} }
/** /**
@@ -122,13 +123,13 @@ public class ChartFragmentTest {
chartFragment.setChartByDistance(true); chartFragment.setChartByDistance(true);
// Resets last location and writes first location. // Resets last location and writes first location.
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
double[] point = chartFragment.createPendingPoint(trackPoint1); ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point[0], 0.01); Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
// The second is a same location, just different time. // The second is a same location, just different time.
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
point = chartFragment.createPendingPoint(trackPoint2); point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(0.0, point[0], 0.01); Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
// The third location is a new location, and use metric. // The third location is a new location, and use metric.
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
@@ -140,7 +141,7 @@ public class ChartFragmentTest {
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(), Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(),
trackPoint3.getLatitude(), trackPoint3.getLongitude(), results); trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
double distance1 = results[0] * UnitConversions.M_TO_KM; double distance1 = results[0] * UnitConversions.M_TO_KM;
Assert.assertEquals(distance1, point[0], 0.01); Assert.assertEquals(distance1, point.getTimeOrDistance(), 0.01);
// The fourth location is a new location, and use metric. // The fourth location is a new location, and use metric.
TrackPoint trackPoint4 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint4 = TrackStubUtils.createDefaultTrackPoint();
@@ -151,7 +152,7 @@ public class ChartFragmentTest {
Location.distanceBetween(trackPoint3.getLatitude(), trackPoint3.getLongitude(), Location.distanceBetween(trackPoint3.getLatitude(), trackPoint3.getLongitude(),
trackPoint4.getLatitude(), trackPoint4.getLongitude(), results); trackPoint4.getLatitude(), trackPoint4.getLongitude(), results);
double distance2 = results[0] * UnitConversions.M_TO_KM; double distance2 = results[0] * UnitConversions.M_TO_KM;
Assert.assertEquals((distance1 + distance2), point[0], 0.01); Assert.assertEquals((distance1 + distance2), point.getTimeOrDistance(), 0.01);
} }
/** /**
@@ -166,8 +167,8 @@ public class ChartFragmentTest {
// The first is a same location, just different time. // The first is a same location, just different time.
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
double[] point = chartFragment.createPendingPoint(trackPoint1); ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point[0], 0.01); Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
// The second location is a new location, and use imperial. // The second location is a new location, and use imperial.
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
@@ -181,7 +182,7 @@ public class ChartFragmentTest {
float[] results = new float[4]; float[] results = new float[4];
Location.distanceBetween(trackPoint1.getLatitude(), trackPoint1.getLongitude(), trackPoint2.getLatitude(), trackPoint2.getLongitude(), results); Location.distanceBetween(trackPoint1.getLatitude(), trackPoint1.getLongitude(), trackPoint2.getLatitude(), trackPoint2.getLongitude(), results);
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI; double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
Assert.assertEquals(distance1, point[0], 0.01); Assert.assertEquals(distance1, point.getTimeOrDistance(), 0.01);
// The third location is a new location, and use imperial. // The third location is a new location, and use imperial.
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
@@ -194,7 +195,7 @@ public class ChartFragmentTest {
*/ */
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(), trackPoint3.getLatitude(), trackPoint3.getLongitude(), results); Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(), trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI; double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
Assert.assertEquals(distance1 + distance2, point[0], 0.01); Assert.assertEquals(distance1 + distance2, point.getTimeOrDistance(), 0.01);
} }
/** /**
@@ -204,13 +205,13 @@ public class ChartFragmentTest {
public void testCreatePendingPoint_time() { public void testCreatePendingPoint_time() {
chartFragment.setChartByDistance(false); chartFragment.setChartByDistance(false);
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
double[] point = chartFragment.createPendingPoint(trackPoint1); ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point[0], 0.01); Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
long timeSpan = 222; long timeSpan = 222;
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
trackPoint2.setTime(trackPoint1.getTime() + timeSpan); trackPoint2.setTime(trackPoint1.getTime() + timeSpan);
point = chartFragment.createPendingPoint(trackPoint2); point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals((double) timeSpan, point[0], 0.01); Assert.assertEquals((double) timeSpan, point.getTimeOrDistance(), 0.01);
} }
/** /**
@@ -224,8 +225,8 @@ public class ChartFragmentTest {
* At first, clear old points of elevation, so give true to the second parameter. * At first, clear old points of elevation, so give true to the second parameter.
* Then only one value INITIAL_ALTITUDE in buffer. * Then only one value INITIAL_ALTITUDE in buffer.
*/ */
double[] point = chartFragment.createPendingPoint(trackPoint1); ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1], 0.01); Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point.getElevation(), 0.01);
/* /*
* Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2. * Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2.
@@ -233,7 +234,7 @@ public class ChartFragmentTest {
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
trackPoint2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2); trackPoint2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
point = chartFragment.createPendingPoint(trackPoint2); point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point[ChartView.ELEVATION_SERIES + 1], 0.01); Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point.getElevation(), 0.01);
} }
/** /**
@@ -249,8 +250,8 @@ public class ChartFragmentTest {
*/ */
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setSpeed(128.5f); trackPoint1.setSpeed(128.5f);
double[] point = chartFragment.createPendingPoint(trackPoint1); ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01); Assert.assertEquals(0.0, point.getSpeed(), 0.01);
/* /*
* Tests the logic when both metricUnits and reportSpeed are true. * Tests the logic when both metricUnits and reportSpeed are true.
@@ -264,7 +265,7 @@ public class ChartFragmentTest {
trackPoint2.setTime(trackPoint1.getTime() + 222); trackPoint2.setTime(trackPoint1.getTime() + 222);
trackPoint2.setSpeed(130); trackPoint2.setSpeed(130);
point = chartFragment.createPendingPoint(trackPoint2); point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[ChartView.SPEED_SERIES + 1], 0.01); Assert.assertEquals(130.0 * UnitConversions.MS_TO_KMH, point.getSpeed(), 0.01);
} }
/** /**
@@ -277,8 +278,8 @@ public class ChartFragmentTest {
// First data point is not added to the speed buffer // First data point is not added to the speed buffer
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setSpeed(100.0f); trackPoint1.setSpeed(100.0f);
double[] point = chartFragment.createPendingPoint(trackPoint1); ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01); Assert.assertEquals(0.0, point.getSpeed(), 0.01);
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
@@ -289,7 +290,7 @@ public class ChartFragmentTest {
trackPoint2.setTime(trackPoint2.getTime() + 222); trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102); trackPoint2.setSpeed(102);
point = chartFragment.createPendingPoint(trackPoint2); point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point[ChartView.SPEED_SERIES + 1], 0.01); Assert.assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
} }
/** /**
@@ -302,8 +303,8 @@ public class ChartFragmentTest {
// First data point is not added to the speed buffer // First data point is not added to the speed buffer
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setSpeed(100.0f); trackPoint1.setSpeed(100.0f);
double[] point = chartFragment.createPendingPoint(trackPoint1); ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01); Assert.assertEquals(0.0, point.getSpeed(), 0.01);
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
@@ -314,7 +315,7 @@ public class ChartFragmentTest {
trackPoint2.setTime(trackPoint2.getTime() + 222); trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102); trackPoint2.setSpeed(102);
point = chartFragment.createPendingPoint(trackPoint2); point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point[ChartView.PACE_SERIES + 1], 0.01); Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point.getPace(), 0.01);
} }
/** /**
@@ -325,7 +326,7 @@ public class ChartFragmentTest {
chartFragment.setReportSpeed(false); chartFragment.setReportSpeed(false);
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
trackPoint.setSpeed(0); trackPoint.setSpeed(0);
double[] point = chartFragment.createPendingPoint(trackPoint); ChartPoint point = chartFragment.createPendingPoint(trackPoint);
Assert.assertEquals(0.0, point[ChartView.PACE_SERIES + 1], 0.01); Assert.assertEquals(0.0, point.getPace(), 0.01);
} }
} }
@@ -96,7 +96,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
if (recordingTrackId == -1L) { if (recordingTrackId == -1L) {
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) { if (service == null) {
Log.d(TAG, "it could not get service"); Log.d(TAG, "could not get TrackRecordingService");
return; return;
} }
@@ -0,0 +1,103 @@
package de.dennisguse.opentracks.chart;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.UnitConversions;
public class ChartPoint {
//X-axis
private double timeOrDistance;
//Y-axis
private double elevation;
private double speed;
private double pace;
private double heartRate = Double.NaN;
private double cadence = Double.NaN;
private double power = Double.NaN;
@VisibleForTesting
ChartPoint(double elevation) {
this.elevation = elevation;
}
public ChartPoint(@NonNull TrackStatisticsUpdater trackStatisticsUpdater, SensorDataSet sensorDataSet, boolean chartByDistance, boolean metricUnits) {
TrackStatistics trackStatistics = trackStatisticsUpdater.getTrackStatistics();
if (chartByDistance) {
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
}
timeOrDistance = distance;
} else {
timeOrDistance = trackStatistics.getTotalTime();
}
elevation = trackStatisticsUpdater.getSmoothedElevation();
if (!metricUnits) {
elevation *= UnitConversions.M_TO_FT;
}
speed = trackStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH;
if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI;
}
pace = speed == 0 ? 0.0 : 60.0 / speed;
if (sensorDataSet != null) {
if (sensorDataSet.hasHeartRate()) {
heartRate = sensorDataSet.getHeartRate();
}
if (sensorDataSet.hasCadence()) {
cadence = sensorDataSet.getCadence();
}
if (sensorDataSet.hasPower()) {
power = sensorDataSet.getPower();
}
}
}
public double getTimeOrDistance() {
return timeOrDistance;
}
public double getElevation() {
return elevation;
}
public double getSpeed() {
return speed;
}
public double getPace() {
return pace;
}
public boolean hasHeartRate() {
return Double.isNaN(heartRate);
}
public double getHeartRate() {
return heartRate;
}
public boolean hasCadence() {
return Double.isNaN(cadence);
}
public double getCadence() {
return cadence;
}
public boolean hasPower() {
return Double.isNaN(power);
}
public double getPower() {
return power;
}
}
@@ -23,6 +23,8 @@ import android.graphics.Paint.Align;
import android.graphics.Paint.Style; import android.graphics.Paint.Style;
import android.graphics.Path; import android.graphics.Path;
import androidx.annotation.NonNull;
import java.text.NumberFormat; import java.text.NumberFormat;
import de.dennisguse.opentracks.stats.ExtremityMonitor; import de.dennisguse.opentracks.stats.ExtremityMonitor;
@@ -32,7 +34,7 @@ import de.dennisguse.opentracks.stats.ExtremityMonitor;
* *
* @author Sandor Dornbush * @author Sandor Dornbush
*/ */
class ChartValueSeries { abstract class ChartValueSeries {
private static final float STROKE_WIDTH = 2f; private static final float STROKE_WIDTH = 2f;
@@ -126,26 +128,26 @@ class ChartValueSeries {
} }
/** /**
* Updates the series with a new value. * Updates the series with a new {@link ChartPoint}.
*
* @param value the new value
*/ */
void update(double value) { void update(ChartPoint chartPoint) {
extremityMonitor.update(value); if (isChartPointValid(chartPoint)) {
extremityMonitor.update(extractDataFromChartPoint(chartPoint));
}
} }
/** abstract double extractDataFromChartPoint(@NonNull ChartPoint chartPoint);
* Gets the path.
*/ boolean isChartPointValid(@NonNull ChartPoint chartPoint) {
return !Double.isNaN(extractDataFromChartPoint(chartPoint));
}
protected abstract boolean drawIfChartPointHasNoData();
Path getPath() { Path getPath() {
return path; return path;
} }
/**
* Draws the path on canvas.
*
* @param canvas the canvas
*/
void drawPath(Canvas canvas) { void drawPath(Canvas canvas) {
canvas.drawPath(path, fillPaint); canvas.drawPath(path, fillPaint);
canvas.drawPath(path, strokePaint); canvas.drawPath(path, strokePaint);
@@ -33,6 +33,7 @@ import android.view.ScaleGestureDetector;
import android.view.View; import android.view.View;
import android.widget.Scroller; import android.widget.Scroller;
import androidx.annotation.NonNull;
import androidx.core.view.GestureDetectorCompat; import androidx.core.view.GestureDetectorCompat;
import java.text.NumberFormat; import java.text.NumberFormat;
@@ -62,14 +63,6 @@ public class ChartView extends View {
static final int Y_AXIS_INTERVALS = 5; static final int Y_AXIS_INTERVALS = 5;
public static final int NUM_SERIES = 6;
public static final int ELEVATION_SERIES = 0;
public static final int SPEED_SERIES = 1;
public static final int PACE_SERIES = 2;
public static final int HEART_RATE_SERIES = 3;
public static final int CADENCE_SERIES = 4;
public static final int POWER_SERIES = 5;
private static final int TARGET_X_AXIS_INTERVALS = 4; private static final int TARGET_X_AXIS_INTERVALS = 4;
private static final int MIN_ZOOM_LEVEL = 1; private static final int MIN_ZOOM_LEVEL = 1;
@@ -89,8 +82,11 @@ public class ChartView extends View {
X_FRACTION_FORMAT.setMinimumFractionDigits(1); X_FRACTION_FORMAT.setMinimumFractionDigits(1);
} }
private final ChartValueSeries[] series = new ChartValueSeries[NUM_SERIES]; private final List<ChartValueSeries> seriesList = new ArrayList<>();
private final List<double[]> chartData = new ArrayList<>(); private final ChartValueSeries speedSeries;
private final ChartValueSeries paceSeries;
private final List<ChartPoint> chartData = new ArrayList<>();
private final List<Waypoint> waypoints = new ArrayList<>(); private final List<Waypoint> waypoints = new ArrayList<>();
private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor(); private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor();
private final Paint axisPaint; private final Paint axisPaint;
@@ -192,58 +188,124 @@ public class ChartView extends View {
} }
}); });
public ChartView(Context context, boolean chartByDistance) { public ChartView(Context context, final boolean chartByDistance) {
super(context); super(context);
this.chartByDistance = chartByDistance; this.chartByDistance = chartByDistance;
series[ELEVATION_SERIES] = new ChartValueSeries(context, seriesList.add(new ChartValueSeries(context,
Integer.MIN_VALUE, Integer.MIN_VALUE,
Integer.MAX_VALUE, Integer.MAX_VALUE,
new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000}, new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000},
R.string.description_elevation_metric, R.string.description_elevation_metric,
R.string.description_elevation_imperial, R.string.description_elevation_imperial,
R.color.chart_elevation_fill, R.color.chart_elevation_fill,
R.color.chart_elevation_border); R.color.chart_elevation_border) {
series[SPEED_SERIES] = new ChartValueSeries(context, @Override
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getElevation();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return true;
}
});
speedSeries = new ChartValueSeries(context,
0, 0,
Integer.MAX_VALUE, Integer.MAX_VALUE,
new int[]{1, 5, 10, 20, 50, 100}, new int[]{1, 5, 10, 20, 50, 100},
R.string.description_speed_metric, R.string.description_speed_metric,
R.string.description_speed_imperial, R.string.description_speed_imperial,
R.color.chart_speed_fill, R.color.chart_speed_fill,
R.color.chart_speed_border); R.color.chart_speed_border) {
series[PACE_SERIES] = new ChartValueSeries(context, @Override
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getSpeed();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return reportSpeed;
}
};
seriesList.add(speedSeries);
paceSeries = new ChartValueSeries(context,
0, 0,
Integer.MAX_VALUE, Integer.MAX_VALUE,
new int[]{1, 2, 5, 10, 15, 20, 30, 60, 120}, new int[]{1, 2, 5, 10, 15, 20, 30, 60, 120},
R.string.description_pace_metric, R.string.description_pace_metric,
R.string.description_pace_imperial, R.string.description_pace_imperial,
R.color.chart_pace_fill, R.color.chart_pace_fill,
R.color.chart_pace_border); R.color.chart_pace_border) {
series[HEART_RATE_SERIES] = new ChartValueSeries(context, @Override
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getPace();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return !reportSpeed;
}
};
seriesList.add(paceSeries);
seriesList.add(new ChartValueSeries(context,
0, 0,
Integer.MAX_VALUE, Integer.MAX_VALUE,
new int[]{25, 50}, new int[]{25, 50},
R.string.description_sensor_heart_rate, R.string.description_sensor_heart_rate,
R.string.description_sensor_heart_rate, R.string.description_sensor_heart_rate,
R.color.chart_heart_rate_fill, R.color.chart_heart_rate_fill,
R.color.chart_heart_rate_border); R.color.chart_heart_rate_border) {
series[CADENCE_SERIES] = new ChartValueSeries(context, @Override
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getHeartRate();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return false;
}
});
seriesList.add(new ChartValueSeries(context,
0, 0,
Integer.MAX_VALUE, Integer.MAX_VALUE,
new int[]{5, 10, 25, 50}, new int[]{5, 10, 25, 50},
R.string.description_sensor_cadence, R.string.description_sensor_cadence,
R.string.description_sensor_cadence, R.string.description_sensor_cadence,
R.color.chart_cadence_fill, R.color.chart_cadence_fill,
R.color.chart_cadence_border); R.color.chart_cadence_border) {
series[POWER_SERIES] = new ChartValueSeries(context, @Override
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getCadence();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return false;
}
});
seriesList.add(new ChartValueSeries(context,
0, 0,
1000, 1000,
new int[]{5, 50, 100, 200}, new int[]{5, 50, 100, 200},
R.string.description_sensor_power, R.string.description_sensor_power,
R.string.description_sensor_power, R.string.description_sensor_power,
R.color.chart_power_fill, R.color.chart_power_fill,
R.color.chart_power_border); R.color.chart_power_border) {
@Override
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getPower();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return false;
}
});
float scale = context.getResources().getDisplayMetrics().density; float scale = context.getResources().getDisplayMetrics().density;
@@ -279,6 +341,10 @@ public class ChartView extends View {
setFocusable(true); setFocusable(true);
setClickable(true); setClickable(true);
updateDimensions(); updateDimensions();
// either speedSeries or paceSeries should be enabled.
speedSeries.setEnabled(reportSpeed);
paceSeries.setEnabled(!reportSpeed);
} }
@Override @Override
@@ -286,15 +352,6 @@ public class ChartView extends View {
return true; return true;
} }
/**
* Sets the enabled value for a chart value series.
*
* @param index the chart value series index
*/
public void setChartValueSeriesEnabled(int index, boolean enabled) {
series[index].setEnabled(enabled);
}
public boolean getMetricUnits() { public boolean getMetricUnits() {
return metricUnits; return metricUnits;
} }
@@ -315,12 +372,30 @@ public class ChartView extends View {
/** /**
* Sets report speed. * Sets report speed.
* *
* @param value true to report speed * @param value report speed (true) or pace (false)
*/ */
public void setReportSpeed(boolean value) { public void setReportSpeed(boolean value) {
reportSpeed = value; reportSpeed = value;
} }
public boolean applyReportSpeed() {
if (reportSpeed) {
if (!speedSeries.isEnabled()) {
speedSeries.setEnabled(true);
paceSeries.setEnabled(false);
return true;
}
} else {
if (!paceSeries.isEnabled()) {
speedSeries.setEnabled(false);
paceSeries.setEnabled(true);
return true;
}
}
return false;
}
/** /**
* Sets show pointer. * Sets show pointer.
* *
@@ -335,15 +410,13 @@ public class ChartView extends View {
* *
* @param dataPoints an array of data points to be added * @param dataPoints an array of data points to be added
*/ */
public void addPendingPoints(List<double[]> dataPoints) { public void addChartPoints(List<ChartPoint> dataPoints) {
synchronized (chartData) { synchronized (chartData) {
chartData.addAll(dataPoints); chartData.addAll(dataPoints);
for (double[] dataPoint : dataPoints) { for (ChartPoint dataPoint : dataPoints) {
xExtremityMonitor.update(dataPoint[0]); xExtremityMonitor.update(dataPoint.getTimeOrDistance());
for (int j = 0; j < series.length; j++) { for (ChartValueSeries i : seriesList) {
if (!Double.isNaN(dataPoint[j + 1])) { i.update(dataPoint);
series[j].update(dataPoint[j + 1]);
}
} }
} }
updateDimensions(); updateDimensions();
@@ -536,7 +609,7 @@ public class ChartView extends View {
* @param canvas the canvas * @param canvas the canvas
*/ */
private void drawDataSeries(Canvas canvas) { private void drawDataSeries(Canvas canvas) {
for (ChartValueSeries chartValueSeries : series) { for (ChartValueSeries chartValueSeries : seriesList) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) { if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
chartValueSeries.drawPath(canvas); chartValueSeries.drawPath(canvas);
} }
@@ -602,9 +675,8 @@ public class ChartView extends View {
int lines = titleDimensions[0]; int lines = titleDimensions[0];
int lineHeight = titleDimensions[1]; int lineHeight = titleDimensions[1];
int count = 0; int count = 0;
for (int i = 0; i < series.length; i++) { for (ChartValueSeries chartValueSeries : seriesList) {
ChartValueSeries chartValueSeries = series[i]; if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) {
count++; count++;
String title = getContext().getString(chartValueSeries.getTitleId(metricUnits)); String title = getContext().getString(chartValueSeries.getTitleId(metricUnits));
Paint paint = chartValueSeries.getTitlePaint(); Paint paint = chartValueSeries.getTitlePaint();
@@ -622,9 +694,8 @@ public class ChartView extends View {
private int[] getTitleDimensions() { private int[] getTitleDimensions() {
int lines = 0; int lines = 0;
int lineHeight = 0; int lineHeight = 0;
for (int i = 0; i < series.length; i++) { for (ChartValueSeries chartValueSeries : seriesList) {
ChartValueSeries chartValueSeries = series[i]; if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) {
lines++; lines++;
String title = getContext().getString(chartValueSeries.getTitleId(metricUnits)); String title = getContext().getString(chartValueSeries.getTitleId(metricUnits));
Rect rect = getRect(chartValueSeries.getTitlePaint(), title); Rect rect = getRect(chartValueSeries.getTitlePaint(), title);
@@ -728,10 +799,10 @@ public class ChartView extends View {
canvas.drawLine(x, y, x, y + effectiveHeight, axisPaint); canvas.drawLine(x, y, x, y + effectiveHeight, axisPaint);
int markerXPosition = x - spacer; int markerXPosition = x - spacer;
for (int i = 0; i < series.length; i++) { for (int i = 0; i < seriesList.size(); i++) {
int index = series.length - 1 - i; int index = seriesList.size() - 1 - i;
ChartValueSeries chartValueSeries = series[index]; ChartValueSeries chartValueSeries = seriesList.get(index);
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(index)) { if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
markerXPosition -= drawYAxisMarkers(chartValueSeries, canvas, markerXPosition) + spacer; markerXPosition -= drawYAxisMarkers(chartValueSeries, canvas, markerXPosition) + spacer;
} }
} }
@@ -779,18 +850,34 @@ public class ChartView extends View {
* @param canvas the canvas * @param canvas the canvas
*/ */
private void drawPointer(Canvas canvas) { private void drawPointer(Canvas canvas) {
int index = -1; //TODO pass chartValueSeries to 2nd if
for (int i = 0; i < series.length; i++) { // int index = -1;
ChartValueSeries chartValueSeries = series[i]; // for (int i = 0; i < seriesList.size(); i++) {
// ChartValueSeries chartValueSeries = seriesList.get(i);
// if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
// index = i;
// break;
// }
// }
// if (index != -1 && chartData.size() > 0) {
// int dx = getX(maxX) - pointer.getIntrinsicWidth() / 2;
// int dy = getY(seriesList.get(index), chartData.get(chartData.size() - 1)[index + 1])
// - pointer.getIntrinsicHeight();
// canvas.translate(dx, dy);
// pointer.draw(canvas);
// }
ChartValueSeries firstChartValueSeries = null;
for (ChartValueSeries chartValueSeries : seriesList) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) { if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
index = i; firstChartValueSeries = chartValueSeries;
break; break;
} }
} }
if (index != -1 && chartData.size() > 0) { if (firstChartValueSeries != null && chartData.size() > 0) {
int dx = getX(maxX) - pointer.getIntrinsicWidth() / 2; int dx = getX(maxX) - pointer.getIntrinsicWidth() / 2;
int dy = getY(series[index], chartData.get(chartData.size() - 1)[index + 1]) double value = firstChartValueSeries.extractDataFromChartPoint(chartData.get(chartData.size() - 1));
- pointer.getIntrinsicHeight(); int dy = getY(firstChartValueSeries, value) - pointer.getIntrinsicHeight();
canvas.translate(dx, dy); canvas.translate(dx, dy);
pointer.draw(canvas); pointer.draw(canvas);
} }
@@ -801,7 +888,7 @@ public class ChartView extends View {
*/ */
private void updatePaths() { private void updatePaths() {
synchronized (chartData) { synchronized (chartData) {
for (ChartValueSeries chartValueSeries : series) { for (ChartValueSeries chartValueSeries : seriesList) {
chartValueSeries.getPath().reset(); chartValueSeries.getPath().reset();
} }
drawPaths(); drawPaths();
@@ -813,17 +900,19 @@ public class ChartView extends View {
* Draws all paths. * Draws all paths.
*/ */
private void drawPaths() { private void drawPaths() {
boolean[] hasMoved = new boolean[series.length]; boolean[] hasMoved = new boolean[seriesList.size()];
for (double[] dataPoint : chartData) { for (ChartPoint dataPoint : chartData) {
for (int j = 0; j < series.length; j++) { for (int j = 0; j < seriesList.size(); j++) {
double value = dataPoint[j + 1]; ChartValueSeries chartValueSeries = seriesList.get(j);
double value = chartValueSeries.extractDataFromChartPoint(dataPoint);
if (Double.isNaN(value)) { if (Double.isNaN(value)) {
continue; continue;
} }
ChartValueSeries chartValueSeries = series[j];
Path path = chartValueSeries.getPath(); Path path = chartValueSeries.getPath();
int x = getX(dataPoint[0]); int x = getX(dataPoint.getTimeOrDistance());
int y = getY(chartValueSeries, value); int y = getY(chartValueSeries, value);
if (!hasMoved[j]) { if (!hasMoved[j]) {
hasMoved[j] = true; hasMoved[j] = true;
@@ -839,20 +928,21 @@ public class ChartView extends View {
* Closes all paths. * Closes all paths.
*/ */
private void closePaths() { private void closePaths() {
for (int i = 0; i < series.length; i++) { for (int i = 0; i < seriesList.size(); i++) {
int first = getFirstPopulatedChartDataIndex(i); ChartValueSeries chartValueSeries = seriesList.get(i);
int first = getFirstPopulatedChartDataIndex(chartValueSeries);
if (first != -1) { if (first != -1) {
int xCorner = getX(chartData.get(first)[0]); int xCorner = getX(chartData.get(first).getTimeOrDistance());
int yCorner = topBorder + effectiveHeight; int yCorner = topBorder + effectiveHeight;
ChartValueSeries chartValueSeries = series[i];
Path path = chartValueSeries.getPath(); Path path = chartValueSeries.getPath();
// Bottom right corner // Bottom right corner
path.lineTo(getX(chartData.get(chartData.size() - 1)[0]), yCorner); path.lineTo(getX(chartData.get(chartData.size() - 1).getTimeOrDistance()), yCorner);
// Bottom left corner // Bottom left corner
path.lineTo(xCorner, yCorner); path.lineTo(xCorner, yCorner);
// Top right corner // Top right corner
path.lineTo(xCorner, getY(chartValueSeries, chartData.get(first)[i + 1])); double value = chartValueSeries.extractDataFromChartPoint(chartData.get(first));
path.lineTo(xCorner, getY(chartValueSeries, value));
} }
} }
} }
@@ -860,12 +950,10 @@ public class ChartView extends View {
/** /**
* Finds the index of the first data point containing data for a series. * Finds the index of the first data point containing data for a series.
* Returns -1 if no data point contains data for the series. * Returns -1 if no data point contains data for the series.
*
* @param seriesIndex the series's index
*/ */
private int getFirstPopulatedChartDataIndex(int seriesIndex) { private int getFirstPopulatedChartDataIndex(ChartValueSeries chartValueSeries) {
for (int i = 0; i < chartData.size(); i++) { for (int i = 0; i < chartData.size(); i++) {
if (!Double.isNaN(chartData.get(i)[seriesIndex + 1])) { if (chartValueSeries.isChartPointValid(chartData.get(i))) {
return i; return i;
} }
} }
@@ -877,7 +965,7 @@ public class ChartView extends View {
*/ */
private void updateDimensions() { private void updateDimensions() {
maxX = xExtremityMonitor.hasData() ? xExtremityMonitor.getMax() : 1.0; maxX = xExtremityMonitor.hasData() ? xExtremityMonitor.getMax() : 1.0;
for (ChartValueSeries chartValueSeries : series) { for (ChartValueSeries chartValueSeries : seriesList) {
chartValueSeries.updateDimension(); chartValueSeries.updateDimension();
} }
float density = getContext().getResources().getDisplayMetrics().density; float density = getContext().getResources().getDisplayMetrics().density;
@@ -885,9 +973,9 @@ public class ChartView extends View {
yAxisOffset = (int) (density * Y_AXIS_OFFSET); yAxisOffset = (int) (density * Y_AXIS_OFFSET);
int markerLength = 0; int markerLength = 0;
for (int i = 0; i < series.length; i++) { for (int i = 0; i < seriesList.size(); i++) {
ChartValueSeries chartValueSeries = series[i]; ChartValueSeries chartValueSeries = seriesList.get(i);
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) { if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
Rect rect = getRect(chartValueSeries.getMarkerPaint(), chartValueSeries.getLargestMarker()); Rect rect = getRect(chartValueSeries.getMarkerPaint(), chartValueSeries.getLargestMarker());
markerLength += rect.width() + spacer; markerLength += rect.width() + spacer;
} }
@@ -980,22 +1068,12 @@ public class ChartView extends View {
/** /**
* Returns true if the index is allowed when the chartData is empty. * Returns true if the index is allowed when the chartData is empty.
*
* @param index the index
*/ */
private boolean allowIfEmpty(int index) { private boolean allowIfEmpty(ChartValueSeries chartValueSeries) {
if (!chartData.isEmpty()) { if (!chartData.isEmpty()) {
return false; return false;
} }
switch (index) {
case ELEVATION_SERIES: return chartValueSeries.drawIfChartPointHasNoData();
return true;
case SPEED_SERIES:
return reportSpeed;
case PACE_SERIES:
return !reportSpeed;
default:
return false;
}
} }
} }
@@ -33,18 +33,17 @@ import java.util.List;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackDetailActivity; import de.dennisguse.opentracks.TrackDetailActivity;
import de.dennisguse.opentracks.chart.ChartPoint;
import de.dennisguse.opentracks.chart.ChartView; import de.dennisguse.opentracks.chart.ChartView;
import de.dennisguse.opentracks.content.TrackDataHub; import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener; import de.dennisguse.opentracks.content.TrackDataListener;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater; import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.LocationUtils; import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/** /**
* A fragment to display track chart to the user. * A fragment to display track chart to the user.
@@ -66,7 +65,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
return chartFragment; return chartFragment;
} }
private final List<double[]> pendingPoints = new ArrayList<>(); private final List<ChartPoint> pendingPoints = new ArrayList<>();
private TrackDataHub trackDataHub; private TrackDataHub trackDataHub;
@@ -74,11 +73,11 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private TrackStatisticsUpdater trackStatisticsUpdater; private TrackStatisticsUpdater trackStatisticsUpdater;
private long startTime; private long startTime;
//TODO Why is this needed?
private int recordingDistanceInterval; private int recordingDistanceInterval;
// Modes of operation // Modes of operation
private boolean chartByDistance; private boolean chartByDistance;
private final boolean[] chartShow = new boolean[]{true, true, true, true, true, true};
// UI elements // UI elements
private ChartView chartView; private ChartView chartView;
@@ -104,8 +103,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext()); boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext());
if (reportSpeed != chartView.getReportSpeed()) { if (reportSpeed != chartView.getReportSpeed()) {
chartView.setReportSpeed(reportSpeed); chartView.setReportSpeed(reportSpeed);
setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed); chartView.applyReportSpeed();
setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed);
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -236,7 +235,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
@Override @Override
public void onNewTrackPointsDone() { public void onNewTrackPointsDone() {
if (isResumed()) { if (isResumed()) {
chartView.addPendingPoints(pendingPoints); chartView.addChartPoints(pendingPoints);
pendingPoints.clear(); pendingPoints.clear();
runOnUiThread(updateChart); runOnUiThread(updateChart);
} }
@@ -267,37 +266,12 @@ public class ChartFragment extends Fragment implements TrackDataListener {
* Checks the chart settings. * Checks the chart settings.
*/ */
private void checkChartSettings() { private void checkChartSettings() {
boolean needUpdate = false; boolean needUpdate = chartView.applyReportSpeed();
if (setSeriesEnabled(ChartView.SPEED_SERIES, chartView.getReportSpeed())) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.PACE_SERIES, !chartView.getReportSpeed())) {
needUpdate = true;
}
if (needUpdate) { if (needUpdate) {
chartView.postInvalidate(); chartView.postInvalidate();
} }
} }
/**
* Sets the series enabled value.
*
* @param index the series index
* @param value the value
* @return true if changed
*/
private boolean setSeriesEnabled(int index, boolean value) {
if (chartShow[index] != value) {
chartShow[index] = value;
chartView.setChartValueSeriesEnabled(index, value);
return true;
}
return false;
}
/** /**
* Resumes the trackDataHub. * Resumes the trackDataHub.
* Needs to be synchronized because trackDataHub can be accessed by multiple threads. * Needs to be synchronized because trackDataHub can be accessed by multiple threads.
@@ -346,68 +320,10 @@ public class ChartFragment extends Fragment implements TrackDataListener {
} }
} }
/**
* Given a {@link TrackPoint}, fill in a data point. <br>
* data[0] = time/distance <br>
* data[1] = elevation <br>
* data[2] = speed <br>
* data[3] = pace <br>
* data[4] = heart rate <br>
* data[5] = cadence <br>
* data[6] = power <br>
*
* @param trackPoint the trackPoint
*/
@VisibleForTesting @VisibleForTesting
double[] createPendingPoint(@NonNull TrackPoint trackPoint) { ChartPoint createPendingPoint(@NonNull TrackPoint trackPoint) {
double timeOrDistance = Double.NaN; trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
double elevation = Double.NaN; return new ChartPoint(trackStatisticsUpdater, trackPoint.getSensorDataSet(), chartByDistance, chartView.getMetricUnits());
double speed = Double.NaN;
double pace = Double.NaN;
if (trackStatisticsUpdater != null) {
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
TrackStatistics trackStatistics = trackStatisticsUpdater.getTrackStatistics();
if (chartByDistance) {
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
if (!chartView.getMetricUnits()) {
distance *= UnitConversions.KM_TO_MI;
}
timeOrDistance = distance;
} else {
timeOrDistance = trackStatistics.getTotalTime();
}
elevation = trackStatisticsUpdater.getSmoothedElevation();
if (!chartView.getMetricUnits()) {
elevation *= UnitConversions.M_TO_FT;
}
speed = trackStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH;
if (!chartView.getMetricUnits()) {
speed *= UnitConversions.KM_TO_MI;
}
pace = speed == 0 ? 0.0 : 60.0 / speed;
}
double heartRate = Double.NaN;
double cadence = Double.NaN;
double power = Double.NaN;
if (trackPoint.getSensorDataSet() != null) {
SensorDataSet sensorDataSet = trackPoint.getSensorDataSet();
if (sensorDataSet.hasHeartRate()) {
heartRate = sensorDataSet.getHeartRate();
}
if (sensorDataSet.hasCadence()) {
cadence = sensorDataSet.getCadence();
}
if (sensorDataSet.hasPower()) {
power = sensorDataSet.getPower();
}
}
//TODO: Is related to ChartView.ELEVATION_SERIES etc.
return new double[]{timeOrDistance, elevation, speed, pace, heartRate, cadence, power};
} }
@VisibleForTesting @VisibleForTesting
@@ -61,9 +61,9 @@ public class TrackStatisticsUpdater {
private final TrackStatistics trackStatistics; private final TrackStatistics trackStatistics;
// A buffer of the recent elevation readings (m) // A buffer of the recent elevation readings (m)
private final DoubleBuffer elevationBuffer = new DoubleBuffer(ELEVATION_SMOOTHING_FACTOR); private final DoubleBuffer elevationBuffer_m = new DoubleBuffer(ELEVATION_SMOOTHING_FACTOR);
// A buffer of the recent speed readings (m/s) for calculating max speed // A buffer of the recent speed readings (m/s) for calculating max speed
private final DoubleBuffer speedBuffer = new DoubleBuffer(SPEED_SMOOTHING_FACTOR); private final DoubleBuffer speedBuffer_ms = new DoubleBuffer(SPEED_SMOOTHING_FACTOR);
// The current segment's statistics // The current segment's statistics
private TrackStatistics currentSegment; private TrackStatistics currentSegment;
@@ -128,8 +128,8 @@ public class TrackStatisticsUpdater {
currentSegment = init(trackPoint.getLocation().getTime()); currentSegment = init(trackPoint.getLocation().getTime());
lastTrackPoint = null; lastTrackPoint = null;
lastMovingTrackPoint = null; lastMovingTrackPoint = null;
elevationBuffer.reset(); elevationBuffer_m.reset();
speedBuffer.reset(); speedBuffer_ms.reset();
return; return;
} }
@@ -144,7 +144,7 @@ public class TrackStatisticsUpdater {
double movingDistance = lastMovingTrackPoint.distanceTo(trackPoint); double movingDistance = lastMovingTrackPoint.distanceTo(trackPoint);
if (movingDistance < minRecordingDistance && (!trackPoint.hasSpeed() || trackPoint.getSpeed() < MAX_NO_MOVEMENT_SPEED)) { if (movingDistance < minRecordingDistance && (!trackPoint.hasSpeed() || trackPoint.getSpeed() < MAX_NO_MOVEMENT_SPEED)) {
speedBuffer.reset(); speedBuffer_ms.reset();
lastTrackPoint = trackPoint; lastTrackPoint = trackPoint;
return; return;
} }
@@ -181,11 +181,11 @@ public class TrackStatisticsUpdater {
* The elevation readings is noisy so the smoothed elevation is better than the raw elevation for many tasks. * The elevation readings is noisy so the smoothed elevation is better than the raw elevation for many tasks.
*/ */
public double getSmoothedElevation() { public double getSmoothedElevation() {
return elevationBuffer.getAverage(); return elevationBuffer_m.getAverage();
} }
public double getSmoothedSpeed() { public double getSmoothedSpeed() {
return speedBuffer.getAverage(); return speedBuffer_ms.getAverage();
} }
/** /**
@@ -199,11 +199,11 @@ public class TrackStatisticsUpdater {
@VisibleForTesting @VisibleForTesting
private void updateSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) { private void updateSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) {
if (speed < MAX_NO_MOVEMENT_SPEED) { if (speed < MAX_NO_MOVEMENT_SPEED) {
speedBuffer.reset(); speedBuffer_ms.reset();
} else if (isValidSpeed(time, speed, lastLocationTime, lastLocationSpeed)) { } else if (isValidSpeed(time, speed, lastLocationTime, lastLocationSpeed)) {
speedBuffer.setNext(speed); speedBuffer_ms.setNext(speed);
if (speedBuffer.getAverage() > currentSegment.getMaxSpeed()) { if (speedBuffer_ms.getAverage() > currentSegment.getMaxSpeed()) {
currentSegment.setMaxSpeed(speedBuffer.getAverage()); currentSegment.setMaxSpeed(speedBuffer_ms.getAverage());
} }
} else { } else {
Log.d(TAG, "Invalid speed. speed: " + speed + " lastLocationSpeed: " + lastLocationSpeed); Log.d(TAG, "Invalid speed. speed: " + speed + " lastLocationSpeed: " + lastLocationSpeed);
@@ -218,9 +218,9 @@ public class TrackStatisticsUpdater {
@VisibleForTesting @VisibleForTesting
private double updateElevation(double elevation) { private double updateElevation(double elevation) {
// Update elevation using the smoothed average // Update elevation using the smoothed average
double oldAverage = elevationBuffer.getAverage(); double oldAverage = elevationBuffer_m.getAverage();
elevationBuffer.setNext(elevation); elevationBuffer_m.setNext(elevation);
double newAverage = elevationBuffer.getAverage(); double newAverage = elevationBuffer_m.getAverage();
currentSegment.updateElevationExtremities(newAverage); currentSegment.updateElevationExtremities(newAverage);
double difference = newAverage - oldAverage; double difference = newAverage - oldAverage;
@@ -264,8 +264,8 @@ public class TrackStatisticsUpdater {
} }
// Only check if the speed buffer is full. Check that the speed is less than 10X the smoothed average and the speed difference doesn't imply 2g acceleration. // Only check if the speed buffer is full. Check that the speed is less than 10X the smoothed average and the speed difference doesn't imply 2g acceleration.
if (speedBuffer.isFull()) { if (speedBuffer_ms.isFull()) {
double average = speedBuffer.getAverage(); double average = speedBuffer_ms.getAverage();
double diff = Math.abs(average - speed); double diff = Math.abs(average - speed);
return (speed < average * 10) && (diff < MAX_ACCELERATION * timeDifference); return (speed < average * 10) && (diff < MAX_ACCELERATION * timeDifference);
} }