diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml
index 243620cee..2d066523f 100644
--- a/MyTracks/res/values/strings.xml
+++ b/MyTracks/res/values/strings.xml
@@ -497,10 +497,6 @@ limitations under the License.
translation_description="Label for location speed in meters per second.">
Speed (m/s)
-
- Time
-
Speed (km/hr)
+
+ Time
+
Point
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java b/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java
index d771a9f81..5e6a8f01c 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java
@@ -85,13 +85,15 @@ public class ChartValueSeries {
strokePaint.setColor(context.getResources().getColor(strokeColor));
strokePaint.setAntiAlias(true);
+ float scale = context.getResources().getDisplayMetrics().density;
+
// Make copies of the stroke paint with the default thickness
titlePaint = new Paint(strokePaint);
- titlePaint.setTextSize(ChartView.LARGE_TEXT_SIZE);
+ titlePaint.setTextSize(ChartView.MEDIUM_TEXT_SIZE * scale);
titlePaint.setTextAlign(Align.CENTER);
markerPaint = new Paint(strokePaint);
- markerPaint.setTextSize(ChartView.MEDIUM_TEXT_SIZE);
+ markerPaint.setTextSize(ChartView.SMALL_TEXT_SIZE * scale);
markerPaint.setTextAlign(Align.RIGHT);
// Set stroke paint thickness
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartView.java b/MyTracks/src/com/google/android/apps/mytracks/ChartView.java
index 802bd80f9..ae8ae8f30 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartView.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartView.java
@@ -13,6 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
+
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.Waypoint;
@@ -21,7 +22,6 @@ import com.google.android.apps.mytracks.util.IntentUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
-import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import android.content.Intent;
@@ -29,8 +29,10 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
+import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Path;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -43,15 +45,14 @@ import java.util.ArrayList;
/**
* Visualization of the chart.
- *
+ *
* @author Sandor Dornbush
* @author Leif Hendrik Wilden
*/
public class ChartView extends View {
- public static final float LARGE_TEXT_SIZE = 22f;
public static final float MEDIUM_TEXT_SIZE = 18f;
- public static final float SMALL_TEXT_SIZE = 14f;
+ public static final float SMALL_TEXT_SIZE = 12f;
public static final int Y_AXIS_INTERVALS = 5;
@@ -63,135 +64,155 @@ public class ChartView extends View {
public static final int CADENCE_SERIES = 4;
public static final int POWER_SERIES = 5;
- private static final int MIN_ZOOM_LEVEL = 1;
+ private static final int TARGET_X_AXIS_INTERVALS = 4;
+
+ private static final int MIN_ZOOM_LEVEL = 1;
+ private static final int MAX_ZOOM_LEVEL = 10;
+
+ private static final NumberFormat X_NUMBER_FORMAT = NumberFormat.getIntegerInstance();
+ private static final NumberFormat X_FRACTION_FORMAT = NumberFormat.getNumberInstance();
+ static {
+ X_FRACTION_FORMAT.setMaximumFractionDigits(1);
+ X_FRACTION_FORMAT.setMinimumFractionDigits(1);
+ }
+
+ private static final int BORDER = 8;
+ private static final int SPACER = 4;
+ private static final int Y_AXIS_OFFSET = 16;
+
+ // Marker pin x position / marker icon width
+ private static final double MARKER_PIN_X_POSITION_PERCENTAGE = (double) 10 / 32;
+
+ private final ChartValueSeries[] series = new ChartValueSeries[NUM_SERIES];
+ private final ArrayList chartData = new ArrayList();
+ private final ArrayList waypoints = new ArrayList();
+ private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor();
+ private double maxX = 1.0;
+
+ private final Paint axisPaint;
+ private final Paint xAxisMarkerPaint;
+ private final Paint gridPaint;
+ private final Paint markerPaint;
+ private final Paint clearPaint;
+
+ private final Drawable pointer;
+ private final Drawable statisticsMarker;
+ private final Drawable waypointMarker;
+ private final int markerWidth;
+ private final int markerHeight;
- /*
- * Scrolling logic:
- */
private final Scroller scroller;
private VelocityTracker velocityTracker = null;
- /** Position of the last motion event */
- private float lastMotionX;
-
- /*
- * Zoom logic:
- */
+ private float lastMotionEventX = -1;
private int zoomLevel = 1;
- private int maxZoomLevel = 10;
- /*
- * Borders, margins, dimensions (in pixels):
- */
- private int leftBorder = -1;
+ private int leftBorder = BORDER;
+ private int topBorder = BORDER;
+ private int bottomBorder = BORDER;
+ private int rightBorder = BORDER;
+ private int spacer = SPACER;
+ private int yAxisOffset = Y_AXIS_OFFSET;
- /**
- * Unscaled top border of the chart.
- */
- private static final int TOP_BORDER = 16;
-
- /**
- * Device scaled top border of the chart.
- */
- private int topBorder;
-
- /**
- * Unscaled bottom border of the chart.
- */
- private static final float BOTTOM_BORDER = 8;
-
- /**
- * Device scaled bottom border of the chart.
- */
- private int bottomBorder;
-
- private static final int RIGHT_BORDER = 40;
-
- /** Space to leave for drawing the unit labels */
- private static final int UNIT_BORDER = 15;
- private static final int FONT_HEIGHT = 10;
-
- private int w = 0;
- private int h = 0;
+ private int width = 0;
+ private int height = 0;
private int effectiveWidth = 0;
private int effectiveHeight = 0;
- /*
- * Ranges (in data units):
- */
- private double maxX = 1;
-
- private ChartValueSeries[] series;
-
- private final ExtremityMonitor xMonitor = new ExtremityMonitor();
- private static final NumberFormat X_FORMAT = NumberFormat.getIntegerInstance();
- private static final NumberFormat X_SHORT_FORMAT = NumberFormat.getNumberInstance();
-
- static {
- X_SHORT_FORMAT.setMaximumFractionDigits(1);
- X_SHORT_FORMAT.setMinimumFractionDigits(1);
- }
-
- /*
- * Paints etc. used when drawing the chart:
- */
- private final Paint borderPaint = new Paint();
- private final Paint labelPaint = new Paint();
- private final Paint gridPaint = new Paint();
- private final Paint gridBarPaint = new Paint();
- private final Paint clearPaint = new Paint();
- private final Drawable pointer;
- private final Drawable statsMarker;
- private final Drawable waypointMarker;
- private final int markerWidth, markerHeight;
-
- /**
- * The chart data stored as an array of double arrays. Each one dimensional
- * array is composed of [x, y].
- */
- private final ArrayList data = new ArrayList();
-
- /**
- * List of way points to be displayed.
- */
- private final ArrayList waypoints = new ArrayList();
-
+ private boolean chartByDistance = true;
private boolean metricUnits = true;
private boolean showPointer = false;
- private boolean chartByDistance = true;
-
+ /**
+ * Constructor.
+ *
+ * @param context the context
+ */
public ChartView(Context context) {
super(context);
- setUpChartValueSeries(context);
+ series[ELEVATION_SERIES] = new ChartValueSeries(context,
+ Integer.MIN_VALUE,
+ Integer.MAX_VALUE,
+ new int[] { 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000 },
+ R.string.description_elevation_metric,
+ R.string.description_elevation_imperial,
+ R.color.elevation_fill,
+ R.color.elevation_border);
+ series[SPEED_SERIES] = new ChartValueSeries(context,
+ 0,
+ Integer.MAX_VALUE,
+ new int[] {1, 5, 10, 20, 50 },
+ R.string.description_speed_metric,
+ R.string.description_speed_imperial,
+ R.color.speed_fill,
+ R.color.speed_border);
+ series[PACE_SERIES] = new ChartValueSeries(context,
+ 0,
+ Integer.MAX_VALUE,
+ new int[] {1, 2, 5, 10, 15, 20, 30, 60, 120 },
+ R.string.description_pace_metric,
+ R.string.description_pace_imperial,
+ R.color.pace_fill,
+ R.color.pace_border);
+ series[HEART_RATE_SERIES] = new ChartValueSeries(context,
+ 0,
+ Integer.MAX_VALUE,
+ new int[] {25, 50 },
+ R.string.description_sensor_heart_rate,
+ R.string.description_sensor_heart_rate,
+ R.color.heartrate_fill,
+ R.color.heartrate_border);
+ series[CADENCE_SERIES] = new ChartValueSeries(context,
+ 0,
+ Integer.MAX_VALUE,
+ new int[] {5, 10, 25, 50 },
+ R.string.description_sensor_cadence,
+ R.string.description_sensor_cadence,
+ R.color.cadence_fill,
+ R.color.cadence_border);
+ series[POWER_SERIES] = new ChartValueSeries(context,
+ 0,
+ 1000,
+ new int[] { 5, 50, 100, 200 },
+ R.string.description_sensor_power,
+ R.string.description_sensor_power,
+ R.color.power_fill,
+ R.color.power_border);
- labelPaint.setStyle(Style.STROKE);
- labelPaint.setColor(context.getResources().getColor(R.color.black));
- labelPaint.setAntiAlias(true);
+ float scale = context.getResources().getDisplayMetrics().density;
- borderPaint.setStyle(Style.STROKE);
- borderPaint.setColor(context.getResources().getColor(R.color.black));
- borderPaint.setAntiAlias(true);
+ axisPaint = new Paint();
+ axisPaint.setStyle(Style.STROKE);
+ axisPaint.setColor(context.getResources().getColor(R.color.black));
+ axisPaint.setAntiAlias(true);
+ axisPaint.setTextSize(SMALL_TEXT_SIZE * scale);
+ xAxisMarkerPaint = new Paint(axisPaint);
+ xAxisMarkerPaint.setTextAlign(Align.CENTER);
+
+ gridPaint = new Paint();
gridPaint.setStyle(Style.STROKE);
gridPaint.setColor(context.getResources().getColor(R.color.gray));
gridPaint.setAntiAlias(false);
+ gridPaint.setPathEffect(new DashPathEffect(new float[] { 3, 2 }, 0));
- gridBarPaint.set(gridPaint);
- gridBarPaint.setPathEffect(new DashPathEffect(new float[] {3, 2}, 0));
+ markerPaint = new Paint();
+ markerPaint.setStyle(Style.STROKE);
+ markerPaint.setColor(context.getResources().getColor(R.color.gray));
+ markerPaint.setAntiAlias(false);
+ clearPaint = new Paint();
clearPaint.setStyle(Style.FILL);
clearPaint.setColor(context.getResources().getColor(R.color.white));
clearPaint.setAntiAlias(false);
pointer = context.getResources().getDrawable(R.drawable.arrow_180);
- pointer.setBounds(0, 0,
- pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
+ pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
- statsMarker = getResources().getDrawable(R.drawable.yellow_pushpin);
- markerWidth = statsMarker.getIntrinsicWidth();
- markerHeight = statsMarker.getIntrinsicHeight();
- statsMarker.setBounds(0, 0, markerWidth, markerHeight);
+ statisticsMarker = getResources().getDrawable(R.drawable.yellow_pushpin);
+ markerWidth = statisticsMarker.getIntrinsicWidth();
+ markerHeight = statisticsMarker.getIntrinsicHeight();
+ statisticsMarker.setBounds(0, 0, markerWidth, markerHeight);
waypointMarker = getResources().getDrawable(R.drawable.blue_pushpin);
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
@@ -202,112 +223,62 @@ public class ChartView extends View {
updateDimensions();
}
- private void setUpChartValueSeries(Context context) {
- series = new ChartValueSeries[NUM_SERIES];
-
- // Create the value series.
- series[ELEVATION_SERIES] = new ChartValueSeries(context,
- Integer.MIN_VALUE,
- Integer.MAX_VALUE,
- new int[] {5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000},
- R.string.description_elevation_metric,
- R.string.description_elevation_imperial,
- R.color.elevation_fill,
- R.color.elevation_border);
- series[SPEED_SERIES] = new ChartValueSeries(context,
- 0,
- Integer.MAX_VALUE,
- new int[] {1, 5, 10, 20, 50},
- R.string.description_speed_metric,
- R.string.description_speed_imperial,
- R.color.speed_fill,
- R.color.speed_border);
- series[PACE_SERIES] = new ChartValueSeries(context,
- 0,
- Integer.MAX_VALUE,
- new int[] { 1, 2, 5, 10, 15, 20, 30, 60, 120 },
- R.string.description_pace_metric,
- R.string.description_pace_imperial,
- R.color.pace_fill,
- R.color.pace_border);
- series[HEART_RATE_SERIES] = new ChartValueSeries(context,
- 0,
- Integer.MAX_VALUE,
- new int[] {25, 50},
- R.string.description_sensor_heart_rate,
- R.string.description_sensor_heart_rate,
- R.color.heartrate_fill,
- R.color.heartrate_border);
- series[CADENCE_SERIES] = new ChartValueSeries(context,
- 0,
- Integer.MAX_VALUE,
- new int[] {5, 10, 25, 50},
- R.string.description_sensor_cadence,
- R.string.description_sensor_cadence,
- R.color.cadence_fill,
- R.color.cadence_border);
- series[POWER_SERIES] = new ChartValueSeries(context,
- 0,
- 1000,
- new int[] {5, 50, 100, 200},
- R.string.description_sensor_power,
- R.string.description_sensor_power,
- R.color.power_fill,
- R.color.power_border);
- }
-
- public void clearWaypoints() {
- waypoints.clear();
- }
-
- public void addWaypoint(Waypoint waypoint) {
- waypoints.add(waypoint);
+ /**
+ * 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);
}
/**
- * Determines whether the pointer icon is shown on the last data point.
+ * Sets chart by distance. It is expected that after changing this value, data
+ * will be reloaded.
+ *
+ * @param value true for by distance, false for by time
+ */
+ public void setChartByDistance(boolean value) {
+ this.chartByDistance = value;
+ }
+
+ /**
+ * Sets metric units.
+ *
+ * @param metricUnits true to use metric units
+ */
+ public void setMetricUnits(boolean metricUnits) {
+ this.metricUnits = metricUnits;
+ }
+
+ /**
+ * Sets show pointer.
+ *
+ * @param showPointer true to show pointer
*/
public void setShowPointer(boolean showPointer) {
this.showPointer = showPointer;
}
/**
- * Sets whether metric units are used or not.
+ * Adds data points.
+ *
+ * @param dataPoints an array of data points to be added
*/
- public void setMetricUnits(boolean metricUnits) {
- this.metricUnits = metricUnits;
- }
-
- private void addDataPointInternal(double[] theData) {
- xMonitor.update(theData[0]);
- int min = Math.min(series.length, theData.length - 1);
- for (int i = 1; i <= min; i++) {
- if (!Double.isNaN(theData[i])) {
- series[i - 1].update(theData[i]);
- }
- }
- // Fill in the extra's if needed.
- for (int i = theData.length; i < series.length; i++) {
- if (series[i].hasData()) {
- series[i].update(0);
- }
- }
- }
-
- /**
- * Adds multiple data points to the chart.
- *
- * @param theData an array list of data points to be added
- */
- public void addDataPoints(ArrayList theData) {
- synchronized (data) {
- data.addAll(theData);
- for (int i = 0; i < theData.size(); i++) {
- double d[] = theData.get(i);
- addDataPointInternal(d);
+ public void addDataPoints(ArrayList dataPoints) {
+ synchronized (chartData) {
+ chartData.addAll(dataPoints);
+ for (int i = 0; i < dataPoints.size(); i++) {
+ double[] dataPoint = dataPoints.get(i);
+ xExtremityMonitor.update(dataPoint[0]);
+ for (int j = 0; j < series.length; j++) {
+ if (!Double.isNaN(dataPoint[j])) {
+ series[j].update(dataPoint[j + 1]);
+ }
+ }
}
updateDimensions();
- setUpPath();
+ updatePaths();
}
}
@@ -315,107 +286,108 @@ public class ChartView extends View {
* Clears all data.
*/
public void reset() {
- synchronized (data) {
- data.clear();
- xMonitor.reset();
+ synchronized (chartData) {
+ chartData.clear();
+ xExtremityMonitor.reset();
zoomLevel = 1;
updateDimensions();
}
}
+ /**
+ * Resets scroll. To be called on the UI thread.
+ */
public void resetScroll() {
scrollTo(0, 0);
}
/**
- * @return true if the chart can be zoomed into.
+ * Adds a waypoint.
+ *
+ * @param waypoint the waypoint
*/
- public boolean canZoomIn() {
- return zoomLevel < maxZoomLevel;
+ public void addWaypoint(Waypoint waypoint) {
+ waypoints.add(waypoint);
}
/**
- * @return true if the chart can be zoomed out
+ * Clears the waypoints.
+ */
+ public void clearWaypoints() {
+ waypoints.clear();
+ }
+
+ /**
+ * Returns true if can zoom in.
+ */
+ public boolean canZoomIn() {
+ return zoomLevel < MAX_ZOOM_LEVEL;
+ }
+
+ /**
+ * Returns true if can zoom out.
*/
public boolean canZoomOut() {
return zoomLevel > MIN_ZOOM_LEVEL;
}
/**
- * Zooms in one level (factor 2).
+ * Zooms in one level.
*/
public void zoomIn() {
if (canZoomIn()) {
zoomLevel++;
- setUpPath();
+ updatePaths();
invalidate();
}
}
/**
- * Zooms out one level (factor 2).
+ * Zooms out one level.
*/
public void zoomOut() {
if (canZoomOut()) {
zoomLevel--;
scroller.abortAnimation();
int scrollX = getScrollX();
- if (scrollX > effectiveWidth * (zoomLevel - 1)) {
- scrollX = effectiveWidth * (zoomLevel - 1);
+ int maxWidth = effectiveWidth * (zoomLevel - 1);
+ if (scrollX > maxWidth) {
+ scrollX = maxWidth;
scrollTo(scrollX, 0);
}
- setUpPath();
+ updatePaths();
invalidate();
}
}
/**
* Initiates flinging.
- *
- * @param velocityX start velocity (pixels per second)
+ *
+ * @param velocityX velocity of fling in pixels per second
*/
public void fling(int velocityX) {
- scroller.fling(getScrollX(), 0, velocityX, 0, 0,
- effectiveWidth * (zoomLevel - 1), 0, 0);
+ int maxWidth = effectiveWidth * (zoomLevel - 1);
+ scroller.fling(getScrollX(), 0, velocityX, 0, 0, maxWidth, 0, 0);
invalidate();
}
/**
- * Scrolls the view horizontally by the given amount.
- *
- * @param deltaX number of pixels to scroll
+ * Scrolls the view horizontally by a given amount.
+ *
+ * @param deltaX the number of pixels to scroll
*/
public void scrollBy(int deltaX) {
int scrollX = getScrollX() + deltaX;
if (scrollX < 0) {
scrollX = 0;
}
- int available = effectiveWidth * (zoomLevel - 1);
- if (scrollX > available) {
- scrollX = available;
+ int maxWidth = effectiveWidth * (zoomLevel - 1);
+ if (scrollX > maxWidth) {
+ scrollX = maxWidth;
}
scrollTo(scrollX, 0);
}
- /**
- * Sets chart by distance value. It is expected that after changing this
- * value, data will be reloaded.
- *
- * @param value true for by distance, false for by time.
- */
- public void setChartByDistance(boolean value) {
- this.chartByDistance = value;
- }
-
- private int getWaypointX(Waypoint waypoint) {
- if (chartByDistance) {
- double lenghtInKm = waypoint.getLength() * UnitConversions.M_TO_KM;
- return getX(metricUnits ? lenghtInKm : lenghtInKm * UnitConversions.KM_TO_MI);
- } else {
- return getX(waypoint.getDuration());
- }
- }
-
/**
* Called by the parent to indicate that the mScrollX/Y values need to be
* updated. Triggers a redraw during flinging.
@@ -439,52 +411,48 @@ public class ChartView extends View {
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(event);
- final int action = event.getAction();
- final float x = event.getX();
- switch (action) {
+ float x = event.getX();
+ switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
- /*
- * If being flinged and user touches, stop the fling. isFinished will be
- * false if being flinged.
- */
+ // Stop the fling
if (!scroller.isFinished()) {
scroller.abortAnimation();
}
- // Remember where the motion event started
- lastMotionX = x;
+ lastMotionEventX = x;
break;
case MotionEvent.ACTION_MOVE:
+ if (lastMotionEventX == -1) {
+ break;
+ }
// Scroll to follow the motion event
- final int deltaX = (int) (lastMotionX - x);
- lastMotionX = x;
+ int deltaX = (int) (lastMotionEventX - x);
+ lastMotionEventX = x;
if (deltaX < 0) {
if (getScrollX() > 0) {
scrollBy(deltaX);
}
} else if (deltaX > 0) {
- final int availableToScroll =
- effectiveWidth * (zoomLevel - 1) - getScrollX();
+ int availableToScroll = effectiveWidth * (zoomLevel - 1) - getScrollX();
if (availableToScroll > 0) {
scrollBy(Math.min(availableToScroll, deltaX));
}
}
break;
case MotionEvent.ACTION_UP:
- // Check if top area with waypoint markers was touched and find the
- // touched marker if any:
- if (event.getY() < 100) {
- int dmin = Integer.MAX_VALUE;
+ // Check if the y event is within markerHeight of the marker center
+ if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2) < markerHeight) {
+ int minDistance = Integer.MAX_VALUE;
Waypoint nearestWaypoint = null;
for (int i = 0; i < waypoints.size(); i++) {
- final Waypoint waypoint = waypoints.get(i);
- final int d = Math.abs(getWaypointX(waypoint) - (int) event.getX()
- - getScrollX());
- if (d < dmin) {
- dmin = d;
+ Waypoint waypoint = waypoints.get(i);
+ int distance = Math.abs(
+ getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX());
+ if (distance < minDistance) {
+ minDistance = distance;
nearestWaypoint = waypoint;
}
}
- if (nearestWaypoint != null && dmin < 100) {
+ if (nearestWaypoint != null && minDistance < markerWidth) {
Intent intent = IntentUtils.newIntent(getContext(), MarkerDetailActivity.class)
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, nearestWaypoint.getId());
getContext().startActivity(intent);
@@ -495,8 +463,7 @@ public class ChartView extends View {
VelocityTracker myVelocityTracker = velocityTracker;
myVelocityTracker.computeCurrentVelocity(1000);
int initialVelocity = (int) myVelocityTracker.getXVelocity();
- if (Math.abs(initialVelocity) >
- ViewConfiguration.getMinimumFlingVelocity()) {
+ if (Math.abs(initialVelocity) > ViewConfiguration.getMinimumFlingVelocity()) {
fling(-initialVelocity);
}
if (velocityTracker != null) {
@@ -514,159 +481,372 @@ public class ChartView extends View {
View.MeasureSpec.getSize(widthMeasureSpec), View.MeasureSpec.getSize(heightMeasureSpec));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
-
+
@Override
- protected void onDraw(Canvas c) {
- synchronized (data) {
+ protected void onDraw(Canvas canvas) {
+ synchronized (chartData) {
- // Keep original state.
- c.save();
+ canvas.save();
+ canvas.drawColor(Color.WHITE);
- c.drawColor(Color.WHITE);
-
- if (data.isEmpty()) {
- // No data, draw only axes
- drawXAxis(c);
- drawYAxis(c);
- c.restore();
+ if (chartData.isEmpty()) {
+ drawXAxis(canvas);
+ clearYAxisMarkers(canvas);
+ drawYAxis(canvas);
+ canvas.restore();
return;
}
- // Clip to graph drawing space
- c.save();
- clipToGraphSpace(c);
+ canvas.save();
+ clipToGraphArea(canvas);
+ drawDataSeries(canvas);
+ drawWaypoints(canvas);
+ drawGrid(canvas);
+ canvas.restore();
- // Draw the grid and the data on it.
- drawGrid(c);
- drawDataSeries(c);
- drawWaypoints(c);
+ drawAxesAndMarkers(canvas);
+ canvas.restore();
- // Go back to full canvas drawing.
- c.restore();
-
- // Draw the axes and their labels.
- drawAxesAndLabels(c);
-
- // Go back to original state.
- c.restore();
-
- // Draw the pointer
if (showPointer) {
- drawPointer(c);
- }
- }
- }
-
- /** Clips the given canvas to the area where the graph lines should be drawn. */
- private void clipToGraphSpace(Canvas c) {
- c.clipRect(leftBorder + 1 + getScrollX(), topBorder + 1,
- w - RIGHT_BORDER + getScrollX() - 1, h - bottomBorder - 1);
- }
-
- /** Draws the axes and their labels into th e given canvas. */
- private void drawAxesAndLabels(Canvas c) {
- drawXLabels(c);
- drawXAxis(c);
- drawSeriesTitles(c);
-
- c.translate(getScrollX(), 0);
- drawYAxis(c);
- float density = getContext().getResources().getDisplayMetrics().density;
- final int spacer = (int) (5 * density);
- int x = leftBorder - spacer;
- for (ChartValueSeries cvs : series) {
- if (cvs.isEnabled() && cvs.hasData()) {
- x -= drawYLabels(cvs, c, x) + spacer;
- }
- }
- }
-
- /** Draws the current pointer into the given canvas. */
- private void drawPointer(Canvas c) {
- c.translate(getX(maxX) - pointer.getIntrinsicWidth() / 2,
- getY(series[0], data.get(data.size() - 1)[1])
- - pointer.getIntrinsicHeight() / 2 - 12);
- pointer.draw(c);
- }
-
- /** Draws the waypoints into the given canvas. */
- private void drawWaypoints(Canvas c) {
- for (int i = 1; i < waypoints.size(); i++) {
- final Waypoint waypoint = waypoints.get(i);
- if (waypoint.getLocation() == null) {
- continue;
- }
- c.save();
-
- final float x = getWaypointX(waypoint);
- c.drawLine(x, h - bottomBorder, x, topBorder, gridPaint);
- c.translate(x - (float) markerWidth / 2.0f, (float) markerHeight);
- if (waypoints.get(i).getType() == Waypoint.TYPE_STATISTICS) {
- statsMarker.draw(c);
- } else {
- waypointMarker.draw(c);
- }
- c.restore();
- }
- }
-
- /** Draws the data series into the given canvas. */
- private void drawDataSeries(Canvas c) {
- for (ChartValueSeries cvs : series) {
- if (cvs.isEnabled() && cvs.hasData()) {
- cvs.drawPath(c);
- }
- }
- }
-
- /** Draws the colored titles for the data series. */
- private void drawSeriesTitles(Canvas c) {
- int sections = 1;
- for (ChartValueSeries cvs : series) {
- if (cvs.isEnabled() && cvs.hasData()) {
- sections++;
- }
- }
- int j = 0;
- for (ChartValueSeries cvs : series) {
- if (cvs.isEnabled() && cvs.hasData()) {
- int x = (int) (w * (double) ++j / sections) + getScrollX();
- c.drawText(
- getContext().getString(cvs.getTitleId(metricUnits)), x, topBorder, cvs.getTitlePaint());
+ drawPointer(canvas);
}
}
}
/**
- * Sets up the path that is used to draw the chart in onDraw(). The path
- * needs to be updated any time after the data or histogram dimensions change.
+ * Clips a canvas to the graph area.
+ *
+ * @param canvas the canvas
*/
- private void setUpPath() {
- synchronized (data) {
- for (ChartValueSeries cvs : series) {
- cvs.getPath().reset();
+ private void clipToGraphArea(Canvas canvas) {
+ canvas.clipRect(leftBorder + getScrollX(), topBorder,
+ leftBorder + effectiveWidth + getScrollX(), topBorder + effectiveHeight);
+ }
+
+ /**
+ * Draws the data series.
+ *
+ * @param canvas the canvas
+ */
+ private void drawDataSeries(Canvas canvas) {
+ for (ChartValueSeries chartValueSeries : series) {
+ if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
+ chartValueSeries.drawPath(canvas);
+ }
+ }
+ }
+
+ /**
+ * Draws the waypoints.
+ *
+ * @param canvas the canvas
+ */
+ private void drawWaypoints(Canvas canvas) {
+ for (int i = 0; i < waypoints.size(); i++) {
+ final Waypoint waypoint = waypoints.get(i);
+ if (waypoint.getLocation() == null) {
+ continue;
+ }
+ double xValue = getWaypointXValue(waypoint);
+ if (xValue > maxX) {
+ continue;
+ }
+ canvas.save();
+ float x = getX(getWaypointXValue(waypoint));
+ canvas.drawLine(
+ x, topBorder + spacer + markerHeight / 2, x, topBorder + effectiveHeight, markerPaint);
+ canvas.translate(
+ x - (float) (markerWidth * MARKER_PIN_X_POSITION_PERCENTAGE), topBorder + spacer);
+ if (waypoints.get(i).getType() == Waypoint.TYPE_STATISTICS) {
+ statisticsMarker.draw(canvas);
+ } else {
+ waypointMarker.draw(canvas);
+ }
+ canvas.restore();
+ }
+ }
+
+ /**
+ * Draws the grid.
+ *
+ * @param canvas the canvas
+ */
+ private void drawGrid(Canvas canvas) {
+ // X axis grid
+ ArrayList intervals = getXIntervals();
+ for (int i = 1; i < intervals.size(); i++) {
+ int x = getX(intervals.get(i));
+ canvas.drawLine(x, topBorder, x, topBorder + effectiveHeight, gridPaint);
+ }
+ // Y axis grid
+ float rightEdge = getX(maxX);
+ for (int i = 0; i <= Y_AXIS_INTERVALS; i++) {
+ double percentage = (double) i / Y_AXIS_INTERVALS;
+ int range = effectiveHeight - 2 * yAxisOffset;
+ int y = topBorder + yAxisOffset + (int) (percentage * range);
+ canvas.drawLine(leftBorder, y, rightEdge, y, gridPaint);
+ }
+ }
+
+ /**
+ * Draws the axes and markers.
+ *
+ * @param canvas the canvas
+ */
+ private void drawAxesAndMarkers(Canvas canvas) {
+ drawSeriesTitles(canvas);
+ drawXAxis(canvas);
+ drawXAxisMarkers(canvas);
+
+ canvas.translate(getScrollX(), 0);
+ clearYAxisMarkers(canvas);
+ drawYAxis(canvas);
+ int x = leftBorder - spacer;
+ for (int i = series.length - 1; i >= 0; i--) {
+ ChartValueSeries chartValueSeries = series[i];
+ if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
+ x -= drawYAxisMarkers(chartValueSeries, canvas, x) + spacer;
+ }
+ }
+ }
+
+ /**
+ * Draws series titles.
+ *
+ * @param canvas the canvas
+ */
+ private void drawSeriesTitles(Canvas canvas) {
+ int[] titleDimensions = getTitleDimenions();
+ int lines = titleDimensions[0];
+ int lineHeight = titleDimensions[1];
+ int i = 0;
+ for (ChartValueSeries chartValueSeries : series) {
+ if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
+ i++;
+ String title = getContext().getString(chartValueSeries.getTitleId(metricUnits));
+ Paint paint = chartValueSeries.getTitlePaint();
+ int x = (int) (0.5 * width) + getScrollX();
+ int y = topBorder - spacer - (lines - i) * (lineHeight + spacer);
+ canvas.drawText(title, x, y, paint);
+ }
+ }
+ }
+
+ /**
+ * Gets the title dimensions. Returns an array of 2 integers, first element is
+ * the number of lines and the second element is the line height.
+ */
+ private int[] getTitleDimenions() {
+ int lines = 0;
+ int lineHeight = 0;
+ for (ChartValueSeries chartValueSeries : series) {
+ if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
+ lines++;
+ String title = getContext().getString(chartValueSeries.getTitleId(metricUnits));
+ Rect rect = getRect(chartValueSeries.getTitlePaint(), title);
+ if (rect.height() > lineHeight) {
+ lineHeight = rect.height();
+ }
+ }
+ }
+ return new int[] { lines, lineHeight };
+ }
+
+ /**
+ * Draws the x axis.
+ *
+ * @param canvas the canvas
+ */
+ private void drawXAxis(Canvas canvas) {
+ float rightEdge = getX(maxX);
+ int y = topBorder + effectiveHeight;
+ canvas.drawLine(leftBorder, y, rightEdge, y, axisPaint);
+ String label = getXAxisLabel();
+ Rect rect = getRect(axisPaint, label);
+ canvas.drawText(label, rightEdge + spacer, y + ((int) rect.height() / 2), axisPaint);
+ }
+
+ /**
+ * Gets the x axis label.
+ */
+ private String getXAxisLabel() {
+ Context context = getContext();
+ if (chartByDistance) {
+ return metricUnits ? context.getString(R.string.unit_kilometer)
+ : context.getString(R.string.unit_mile);
+ } else {
+ return context.getString(R.string.description_time);
+ }
+ }
+
+ /**
+ * Draws the x axis markers.
+ *
+ * @param canvas the canvas
+ */
+ private void drawXAxisMarkers(Canvas canvas) {
+ ArrayList intervals = getXIntervals();
+ NumberFormat numberFormat = intervals.get(0) < 1 ? X_FRACTION_FORMAT : X_NUMBER_FORMAT;
+ for (int i = 1; i < intervals.size(); i++) {
+ drawXAxisMarker(canvas, intervals.get(i), numberFormat);
+ }
+ }
+
+ /**
+ * Draws a x axis marker.
+ *
+ * @param canvas
+ * @param value value
+ * @param numberFormat the number format
+ */
+ private void drawXAxisMarker(Canvas canvas, double value, NumberFormat numberFormat) {
+ String marker = chartByDistance ? numberFormat.format(value)
+ : StringUtils.formatElapsedTime((long) value);
+ Rect rect = getRect(xAxisMarkerPaint, marker);
+ canvas.drawText(marker, getX(value), topBorder + effectiveHeight + spacer + rect.height(),
+ xAxisMarkerPaint);
+ }
+
+ /**
+ * Gets the x axis intervals. The first element of the returned array contains
+ * the size of the interval.
+ */
+ private ArrayList getXIntervals() {
+ ArrayList intervals = new ArrayList();
+ double interval = maxX / zoomLevel / TARGET_X_AXIS_INTERVALS;
+ if (interval < 1) {
+ interval = .5;
+ } else if (interval < 5) {
+ interval = 2;
+ } else if (interval < 10) {
+ interval = 5;
+ } else {
+ interval = (interval / 10) * 10;
+ }
+ intervals.add(interval);
+ intervals.add(0d);
+ for (int i = 1; i * interval < maxX; i++) {
+ intervals.add(i * interval);
+ }
+ if (intervals.size() < 3) {
+ intervals.add(maxX);
+ }
+ return intervals;
+ }
+
+ /**
+ * Clears the y axis markers.
+ *
+ * @param canvas the canvas
+ */
+ private void clearYAxisMarkers(Canvas canvas) {
+ canvas.drawRect(0, topBorder, leftBorder, topBorder + effectiveHeight + spacer, clearPaint);
+ }
+
+ /**
+ * Draws the y axis.
+ *
+ * @param canvas the canvas
+ */
+ private void drawYAxis(Canvas canvas) {
+ canvas.drawLine(leftBorder, topBorder, leftBorder, topBorder + effectiveHeight, axisPaint);
+ }
+
+ /**
+ * Draws the y axis markers for a chart value series.
+ *
+ * @param chartValueSeries the chart value series
+ * @param canvas the canvas
+ * @param xPosition the right most x position
+ * @return the maximum marker width.
+ */
+ private float drawYAxisMarkers(ChartValueSeries chartValueSeries, Canvas canvas, int xPosition) {
+ int interval = chartValueSeries.getInterval();
+ float maxMarkerWidth = 0;
+ for (int i = 0; i <= Y_AXIS_INTERVALS; i++) {
+ maxMarkerWidth = Math.max(maxMarkerWidth, drawYAxisMarker(chartValueSeries, canvas, xPosition,
+ i * interval + chartValueSeries.getMinMarkerValue()));
+ }
+ return maxMarkerWidth;
+ }
+
+ /**
+ * Draws a y axis marker.
+ *
+ * @param chartValueSeries the chart value series
+ * @param canvas the canvas
+ * @param xPosition the right most x position
+ * @param yValue the y value
+ * @return the marker width.
+ */
+ private float drawYAxisMarker(
+ ChartValueSeries chartValueSeries, Canvas canvas, int xPosition, int yValue) {
+ String marker = chartValueSeries.formatMarker(yValue);
+ Paint paint = chartValueSeries.getMarkerPaint();
+ Rect rect = getRect(paint, marker);
+ int yPosition = getY(chartValueSeries, yValue) + (int) (rect.height() / 2);
+ canvas.drawText(marker, xPosition, yPosition, paint);
+ return paint.measureText(marker);
+ }
+
+ /**
+ * Draws the current pointer.
+ *
+ * @param canvas the canvas
+ */
+ private void drawPointer(Canvas canvas) {
+ int index = -1;
+ for (int i = 0; i < series.length; i++) {
+ ChartValueSeries chartValueSeries = series[i];
+ if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
+ index = i;
+ break;
+ }
+ }
+ if (index != -1) {
+ int dx = getX(maxX) - pointer.getIntrinsicWidth() / 2;
+ int dy = getY(series[index], chartData.get(chartData.size() - 1)[index + 1])
+ - pointer.getIntrinsicHeight();
+ canvas.translate(dx, dy);
+ pointer.draw(canvas);
+ }
+ }
+
+ /**
+ * Updates paths. The path needs to be updated any time after the data or the
+ * dimensions change.
+ */
+ private void updatePaths() {
+ synchronized (chartData) {
+ for (ChartValueSeries chartValueSeries : series) {
+ chartValueSeries.getPath().reset();
}
- if (!data.isEmpty()) {
+ if (!chartData.isEmpty()) {
drawPaths();
closePaths();
}
}
}
- /** Actually draws the data points as a path. */
+ /**
+ * Draws all paths.
+ */
private void drawPaths() {
- // All of the data points to the respective series.
- // TODO: Come up with a better sampling than Math.max(1, (maxZoomLevel - zoomLevel + 1) / 2);
+ // TODO: Come up with a better sampling.
+ // E.g., Math.max(1, (maxZoomLevel - zoomLevel + 1) / 2)
int sampling = 1;
- for (int i = 0; i < data.size(); i += sampling) {
- double[] d = data.get(i);
- int min = Math.min(series.length, d.length - 1);
- for (int j = 0; j < min; ++j) {
- ChartValueSeries cvs = series[j];
- Path path = cvs.getPath();
- int x = getX(d[0]);
- int y = getY(cvs, d[j + 1]);
+ for (int i = 0; i < chartData.size(); i += sampling) {
+ double[] dataPoint = chartData.get(i);
+ for (int j = 0; j < series.length; j++) {
+ double value = dataPoint[j + 1];
+ if (Double.isNaN(value)) {
+ continue;
+ }
+ ChartValueSeries chartValueSeries = series[j];
+ Path path = chartValueSeries.getPath();
+ int x = getX(dataPoint[0]);
+ int y = getY(chartValueSeries, value);
if (i == 0) {
path.moveTo(x, y);
} else {
@@ -676,37 +856,38 @@ public class ChartView extends View {
}
}
- /** Closes the drawn path so it looks like a solid graph. */
+ /**
+ * Closes all paths.
+ */
private void closePaths() {
// Close the path.
int yCorner = topBorder + effectiveHeight;
- int xCorner = getX(data.get(0)[0]);
- int min = series.length;
- for (int j = 0; j < min; j++) {
- ChartValueSeries cvs = series[j];
- Path path = cvs.getPath();
- int first = getFirstPointPopulatedIndex(j + 1);
+ int xCorner = getX(chartData.get(0)[0]);
+ for (int i = 0; i < series.length; i++) {
+ ChartValueSeries chartValueSeries = series[i];
+ Path path = chartValueSeries.getPath();
+ int first = getFirstPopulatedChartDataIndex(i);
+
if (first != -1) {
// Bottom right corner
- path.lineTo(getX(data.get(data.size() - 1)[0]), yCorner);
+ path.lineTo(getX(chartData.get(chartData.size() - 1)[0]), yCorner);
// Bottom left corner
path.lineTo(xCorner, yCorner);
// Top right corner
- path.lineTo(xCorner, getY(cvs, data.get(first)[j + 1]));
+ path.lineTo(xCorner, getY(chartValueSeries, chartData.get(first)[i + 1]));
}
}
}
/**
- * Finds the index of the first point which has a series populated.
- *
- * @param seriesIndex The index of the value series to search for
- * @return The index in the first data for the point in the series that has series
- * index value populated or -1 if none is found
+ * Finds the index of the first data point containing data for a series.
+ * Returns -1 if no data point contains data for the series.
+ *
+ * @param seriesIndex the series's index
*/
- private int getFirstPointPopulatedIndex(int seriesIndex) {
- for (int i = 0; i < data.size(); i++) {
- if (data.get(i).length > seriesIndex) {
+ private int getFirstPopulatedChartDataIndex(int seriesIndex) {
+ for (int i = 0; i < chartData.size(); i++) {
+ if (!Double.isNaN(chartData.get(i)[seriesIndex + 1])) {
return i;
}
}
@@ -717,179 +898,102 @@ public class ChartView extends View {
* Updates the chart dimensions.
*/
private void updateDimensions() {
- maxX = xMonitor.getMax();
- if (data.size() <= 1) {
- maxX = 1;
- }
- for (ChartValueSeries cvs : series) {
- cvs.updateDimension();
- }
- // TODO: This is totally broken. Make sure that we calculate based on measureText for each
- // grid line, as the labels may vary across intervals.
- int maxLength = 0;
- for (ChartValueSeries cvs : series) {
- if (cvs.isEnabled() && cvs.hasData()) {
- maxLength += cvs.getLargestMarker().length();
- }
+ maxX = xExtremityMonitor.hasData() ? xExtremityMonitor.getMax() : 1.0;
+ for (ChartValueSeries chartValueSeries : series) {
+ chartValueSeries.updateDimension();
}
float density = getContext().getResources().getDisplayMetrics().density;
- maxLength = Math.max(maxLength, 1);
- leftBorder = (int) (density * (4 + 8 * maxLength));
- bottomBorder = (int) (density * BOTTOM_BORDER);
- topBorder = (int) (density * TOP_BORDER);
+ spacer = (int) (density * SPACER);
+ yAxisOffset = (int) (density * Y_AXIS_OFFSET);
+
+ int markerLength = 0;
+ for (ChartValueSeries chartValueSeries : series) {
+ if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
+ Rect rect = getRect(chartValueSeries.getMarkerPaint(), chartValueSeries.getLargestMarker());
+ markerLength += rect.width() + spacer;
+ }
+ }
+
+ leftBorder = (int) (density * BORDER + markerLength);
+ int[] titleDimensions = getTitleDimenions();
+ topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer));
+ bottomBorder = (int) (density * BORDER + getRect(xAxisMarkerPaint, "1").height() + spacer);
+ rightBorder = (int) (density * BORDER + getRect(axisPaint, getXAxisLabel()).width() + spacer);
updateEffectiveDimensions();
}
- /** Updates the effective dimensions where the graph will be drawn. */
+ /**
+ * Updates the effective dimensions.
+ */
private void updateEffectiveDimensions() {
- effectiveWidth = Math.max(0, w - leftBorder - RIGHT_BORDER);
- effectiveHeight = Math.max(0, h - topBorder - bottomBorder);
+ effectiveWidth = Math.max(0, width - leftBorder - rightBorder);
+ effectiveHeight = Math.max(0, height - topBorder - bottomBorder);
}
/**
- * Updates the effective dimensions where the graph will be drawn, only if the
- * dimensions have changed since the last call.
- *
+ * Updates the effective dimensions if changed.
+ *
* @param newWidth the new width
* @param newHeight the new height
*/
private void updateEffectiveDimensionsIfChanged(int newWidth, int newHeight) {
- if (w != newWidth || h != newHeight) {
- // Dimensions have changed (for example due to orientation change).
- w = newWidth;
- h = newHeight;
+ if (width != newWidth || height != newHeight) {
+ width = newWidth;
+ height = newHeight;
updateEffectiveDimensions();
- setUpPath();
- }
- }
-
- private int getX(double distance) {
- return leftBorder + (int) ((distance * effectiveWidth / maxX) * zoomLevel);
- }
-
- private int getY(ChartValueSeries cvs, double y) {
- int effectiveSpread = cvs.getInterval() * Y_AXIS_INTERVALS;
- return topBorder + effectiveHeight
- - (int) ((y - cvs.getMinMarkerValue()) * effectiveHeight / effectiveSpread);
- }
-
- /** Draws the labels on the X axis into the given canvas. */
- private void drawXLabels(Canvas c) {
- double interval = (int) (maxX / zoomLevel / 4);
- boolean shortFormat = false;
- if (interval < 1) {
- interval = .5;
- shortFormat = true;
- } else if (interval < 5) {
- interval = 2;
- } else if (interval < 10) {
- interval = 5;
- } else {
- interval = (interval / 10) * 10;
- }
- drawXLabel(c, 0, shortFormat);
- int numLabels = 1;
- for (int i = 1; i * interval < maxX; i++) {
- drawXLabel(c, i * interval, shortFormat);
- numLabels++;
- }
- if (numLabels < 2) {
- drawXLabel(c, (int) maxX, shortFormat);
- }
- }
-
- /** Draws the labels on the Y axis into the given canvas. */
- private float drawYLabels(ChartValueSeries cvs, Canvas c, int x) {
- int interval = cvs.getInterval();
- float maxTextWidth = 0;
- for (int i = 0; i < Y_AXIS_INTERVALS; ++i) {
- maxTextWidth = Math.max(maxTextWidth, drawYLabel(cvs, c, x, i * interval + cvs.getMinMarkerValue()));
- }
- return maxTextWidth;
- }
-
- /** Draws a single label on the X axis. */
- private void drawXLabel(Canvas c, double x, boolean shortFormat) {
- if (x < 0) {
- return;
- }
- String s = chartByDistance
- ? (shortFormat ? X_SHORT_FORMAT.format(x) : X_FORMAT.format(x))
- : StringUtils.formatElapsedTime((long) x);
- c.drawText(s,
- getX(x),
- effectiveHeight + UNIT_BORDER + topBorder,
- labelPaint);
- }
-
- /** Draws a single label on the Y axis. */
- private float drawYLabel(ChartValueSeries cvs, Canvas c, int x, int y) {
- int desiredY = (int) ((y - cvs.getMinMarkerValue()) * effectiveHeight /
- (cvs.getInterval() * Y_AXIS_INTERVALS));
- desiredY = topBorder + effectiveHeight + FONT_HEIGHT / 2 - desiredY - 1;
- Paint markerPaint = cvs.getMarkerPaint();
- String text = cvs.formatMarker(y);
- c.drawText(text, x, desiredY, markerPaint);
- return markerPaint.measureText(text);
- }
-
- /** Draws the actual X axis line and its label. */
- private void drawXAxis(Canvas canvas) {
- float rightEdge = getX(maxX);
- final int y = effectiveHeight + topBorder;
- canvas.drawLine(leftBorder, y, rightEdge, y, borderPaint);
- Context c = getContext();
- String s = chartByDistance
- ? (metricUnits ? c.getString(R.string.unit_kilometer) : c.getString(R.string.unit_mile))
- : c.getString(R.string.unit_minute);
- canvas.drawText(s, rightEdge, effectiveHeight + .2f * UNIT_BORDER + topBorder, labelPaint);
- }
-
- /** Draws the actual Y axis line and its label. */
- private void drawYAxis(Canvas canvas) {
- canvas.drawRect(0, 0,
- leftBorder - 1, effectiveHeight + topBorder + UNIT_BORDER + 1,
- clearPaint);
- canvas.drawLine(leftBorder, UNIT_BORDER + topBorder,
- leftBorder, effectiveHeight + topBorder,
- borderPaint);
- for (int i = 1; i < Y_AXIS_INTERVALS; ++i) {
- int y = i * effectiveHeight / Y_AXIS_INTERVALS + topBorder;
- canvas.drawLine(leftBorder - 5, y, leftBorder, y, gridPaint);
- }
-
- Context c = getContext();
- // TODO: This should really show units for all series.
- String s = metricUnits ? c.getString(R.string.unit_meter) : c.getString(R.string.unit_feet);
- canvas.drawText(s, leftBorder - UNIT_BORDER * .2f, UNIT_BORDER * .8f + topBorder, labelPaint);
- }
-
- /** Draws the grid for the graph. */
- private void drawGrid(Canvas c) {
- float rightEdge = getX(maxX);
- for (int i = 1; i < Y_AXIS_INTERVALS; ++i) {
- int y = i * effectiveHeight / Y_AXIS_INTERVALS + topBorder;
- c.drawLine(leftBorder, y, rightEdge, y, gridBarPaint);
+ updatePaths();
}
}
/**
- * Sets whether a given time series will be enabled for drawing.
- *
- * @param index the time series, one of {@link #ELEVATION_SERIES},
- * {@link #SPEED_SERIES}, {@link #POWER_SERIES}, etc.
+ * Gets the x position for a value.
+ *
+ * @param value the value
*/
- public void setChartValueSeriesEnabled(int index, boolean enabled) {
- series[index].setEnabled(enabled);
+ private int getX(double value) {
+ if (value > maxX) {
+ value = maxX;
+ }
+ double percentage = value / maxX;
+ return leftBorder + (int) (percentage * effectiveWidth * zoomLevel);
}
-
- @VisibleForTesting
- int getZoomLevel() {
- return zoomLevel;
+
+ /**
+ * Gets the y position for a value in a chart value series
+ *
+ * @param chartValueSeries the chart value series
+ * @param value the value
+ */
+ private int getY(ChartValueSeries chartValueSeries, double value) {
+ int effectiveSpread = chartValueSeries.getInterval() * Y_AXIS_INTERVALS;
+ double percentage = (value - chartValueSeries.getMinMarkerValue()) / effectiveSpread;
+ int rangeHeight = effectiveHeight - 2 * yAxisOffset;
+ return topBorder + yAxisOffset + rangeHeight - (int) (percentage * rangeHeight);
}
-
- @VisibleForTesting
- int getMaxZoomLevel() {
- return maxZoomLevel;
+
+ /**
+ * Gets a waypoint's x value.
+ *
+ * @param waypoint the waypoint
+ */
+ private double getWaypointXValue(Waypoint waypoint) {
+ if (chartByDistance) {
+ double lenghtInKm = waypoint.getLength() * UnitConversions.M_TO_KM;
+ return metricUnits ? lenghtInKm : lenghtInKm * UnitConversions.KM_TO_MI;
+ } else {
+ return waypoint.getDuration();
+ }
+ }
+
+ /**
+ * Gets a paint's Rect for a string.
+ *
+ * @param paint the paint
+ * @param string the string
+ */
+ private Rect getRect(Paint paint, String string) {
+ Rect rect = new Rect();
+ paint.getTextBounds(string, 0, string.length(), rect);
+ return rect;
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartFragment.java
index 679129d3e..857e88b03 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartFragment.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartFragment.java
@@ -280,8 +280,10 @@ public class ChartFragment extends Fragment implements TrackDataListener {
return false;
}
reportSpeed = speed;
- setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed);
- setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed);
+ boolean chartShowSpeed = PreferencesUtils.getBoolean(
+ getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
+ setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed);
+ setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -307,12 +309,13 @@ public class ChartFragment extends Fragment implements TrackDataListener {
R.string.chart_show_elevation_key, PreferencesUtils.CHART_SHOW_ELEVATION_DEFAULT))) {
needUpdate = true;
}
- if (setSeriesEnabled(ChartView.SPEED_SERIES, PreferencesUtils.getBoolean(
- getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT))) {
+
+ boolean chartShowSpeed = PreferencesUtils.getBoolean(
+ getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
+ if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) {
needUpdate = true;
}
- if (setSeriesEnabled(ChartView.PACE_SERIES, !PreferencesUtils.getBoolean(
- getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT))) {
+ if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.getBoolean(
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java
index d05e3da05..98c0d769a 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java
@@ -110,7 +110,7 @@ public class CsvTrackWriter implements TrackFormatWriter {
context.getString(R.string.description_location_bearing),
context.getString(R.string.description_location_accuracy),
context.getString(R.string.description_location_speed),
- context.getString(R.string.description_location_time));
+ context.getString(R.string.description_time));
}
@Override
@@ -143,7 +143,7 @@ public class CsvTrackWriter implements TrackFormatWriter {
context.getString(R.string.description_location_bearing),
context.getString(R.string.description_location_accuracy),
context.getString(R.string.description_location_speed),
- context.getString(R.string.description_location_time),
+ context.getString(R.string.description_time),
context.getString(R.string.description_sensor_power),
context.getString(R.string.description_sensor_cadence),
context.getString(R.string.description_sensor_heart_rate),