From 81805f00d82543ffdb66baccecc09b2170b4eb55 Mon Sep 17 00:00:00 2001 From: Jimmy Shih Date: Fri, 25 May 2012 17:28:50 -0700 Subject: [PATCH] Improve chartview layout. --- MyTracks/res/values/strings.xml | 8 +- .../apps/mytracks/ChartValueSeries.java | 6 +- .../android/apps/mytracks/ChartView.java | 1230 +++++++++-------- .../mytracks/fragments/ChartFragment.java | 15 +- .../apps/mytracks/io/file/CsvTrackWriter.java | 4 +- 5 files changed, 686 insertions(+), 577 deletions(-) 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),