diff --git a/MyTracks/res/layout/mytracks_elevation.xml b/MyTracks/res/layout/mytracks_charts.xml
similarity index 94%
rename from MyTracks/res/layout/mytracks_elevation.xml
rename to MyTracks/res/layout/mytracks_charts.xml
index 823ee8be1..e758d30bd 100644
--- a/MyTracks/res/layout/mytracks_elevation.xml
+++ b/MyTracks/res/layout/mytracks_charts.xml
@@ -30,13 +30,15 @@ limitations under the License.
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
+ android:padding="10dip"
android:layout_weight="0">
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentRight="true"/>
#FF000000
#FF0000FF
- #400000FF
#FF444444
#FFffd700
#FFAAAAAA
@@ -30,18 +29,22 @@ limitations under the License.
- #FF00AA00
- #8000AA00
+ #FF339933
+ #40B3CFB3
-
- #FF401465
- #40401465
+
+ #FF0066FF
+ #40C8E0FF
+
+
+ #FFCC3300
+ #40E88A70
#FFF128F3
#40F128F3
-
- #FFFF0000
- #40FF0000
+
+ #FF401465
+ #40401465
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
index 121615121..25757e541 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
@@ -252,7 +252,7 @@ public class ChartActivity extends Activity implements
setVolumeControlStream(volumeStream);
requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.mytracks_elevation);
+ setContentView(R.layout.mytracks_charts);
ViewGroup layout = (ViewGroup) findViewById(R.id.elevation_chart);
cv = new ChartView(this);
cv.setMode(this.mode);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java b/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java
index 31a196bee..57b88125d 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java
@@ -38,16 +38,95 @@ public class ChartValueSeries {
private final Paint fillPaint;
private final Paint strokePaint;
private final Paint labelPaint;
- private final int rounding;
+ private final ZoomSettings zoomSettings;
private String title;
- private double min = 0;
- private double max = 1;
- private int effectiveMax = 0;
- private int absoluteMax = Integer.MIN_VALUE;
- private int effectiveMin = 0;
- private double spread = 0;
+ private double min;
+ private double max = 1.0;
+ private int effectiveMax;
+ private int effectiveMin;
+ private double spread;
+ private int interval;
private boolean enabled = true;
+
+ /**
+ * This class controls how effective min/max values of a {@link ChartValueSeries} are calculated.
+ */
+ public static class ZoomSettings {
+ private int intervals;
+ private final int absoluteMin;
+ private final int absoluteMax;
+ private final int[] zoomLevels;
+
+ public ZoomSettings(int intervals, int[] zoomLevels) {
+ this.intervals = intervals;
+ this.absoluteMin = Integer.MAX_VALUE;
+ this.absoluteMax = Integer.MIN_VALUE;
+ this.zoomLevels = zoomLevels;
+ checkArgs();
+ }
+
+ public ZoomSettings(int intervals, int absoluteMin, int absoluteMax, int[] zoomLevels) {
+ this.intervals = intervals;
+ this.absoluteMin = absoluteMin;
+ this.absoluteMax = absoluteMax;
+ this.zoomLevels = zoomLevels;
+ checkArgs();
+ }
+
+ private void checkArgs() {
+ if (intervals <= 0 || zoomLevels == null || zoomLevels.length == 0) {
+ throw new IllegalArgumentException("Expecing positive intervals and non-empty zoom levels");
+ }
+ for (int i = 1; i < zoomLevels.length; ++i) {
+ if (zoomLevels[i] <= zoomLevels[i - 1]) {
+ throw new IllegalArgumentException("Expecting zoom levels in ascending order");
+ }
+ }
+ }
+
+ public int getIntervals() {
+ return intervals;
+ }
+
+ public int getAbsoluteMin() {
+ return absoluteMin;
+ }
+
+ public int getAbsoluteMax() {
+ return absoluteMax;
+ }
+
+ public int[] getZoomLevels() {
+ return zoomLevels;
+ }
+
+ /**
+ * Calculates the interval between markings given the min and max values.
+ * This function attempts to find the smallest zoom level that fits [min,max] after rounding
+ * it to the current zoom level.
+ *
+ * @param min the minimum value in the series
+ * @param max the maximum value in the series
+ * @return the calculated interval for the given range
+ */
+ public int calculateInterval(double min, double max) {
+ min = Math.min(min, absoluteMin);
+ max = Math.max(max, absoluteMax);
+ for (int i = 0; i < zoomLevels.length; ++i) {
+ int zoomLevel = zoomLevels[i];
+ int roundedMin = (int)(min / zoomLevel) * zoomLevel;
+ if (roundedMin > min) {
+ roundedMin -= zoomLevel;
+ }
+ double interval = (max - roundedMin) / intervals;
+ if (zoomLevel >= interval) {
+ return zoomLevel;
+ }
+ }
+ return zoomLevels[zoomLevels.length - 1];
+ }
+ }
/**
* Constructs a new chart value series.
@@ -56,14 +135,12 @@ public class ChartValueSeries {
* @param formatString The format of the decimal format for this series
* @param fill The paint for filling the chart
* @param stroke The paint for stroking the outside the chart, optional
- * @param rounding The factor to round the values by
+ * @param zoomSettings The settings related to zooming
+ *
+ * TODO: Get rid of Context and inject appropriate values instead.
*/
- public ChartValueSeries(Context context,
- String formatString,
- int fillColor,
- int strokeColor,
- int rounding,
- int titleId) {
+ public ChartValueSeries(Context context, String formatString, int fillColor, int strokeColor,
+ ZoomSettings zoomSettings, int titleId) {
this.format = new DecimalFormat(formatString);
fillPaint = new Paint();
fillPaint.setStyle(Style.FILL);
@@ -81,7 +158,7 @@ public class ChartValueSeries {
strokePaint = null;
labelPaint = fillPaint;
}
- this.rounding = rounding;
+ this.zoomSettings = zoomSettings;
this.title = context.getString(titleId);
}
@@ -113,20 +190,7 @@ public class ChartValueSeries {
* @return The interval between markers
*/
public int getInterval() {
- // Try to find 5 even looking intervals.
- int interval = (int) (spread / 5);
- int minInterval = rounding / 4;
- if (interval < minInterval) {
- // We won't be able to find 5 even intervals.
- return minInterval;
- } else if (interval < rounding) {
- // The desired interval is less than the rounding value.
- // We will have less than 5 intervals.
- return rounding;
- } else {
- // Round the interval.
- return (interval / rounding) * rounding;
- }
+ return interval;
}
/**
@@ -141,14 +205,18 @@ public class ChartValueSeries {
min = monitor.getMin();
max = monitor.getMax();
}
+ min = Math.min(min, zoomSettings.getAbsoluteMin());
+ max = Math.max(max, zoomSettings.getAbsoluteMax());
+
+ this.interval = zoomSettings.calculateInterval(min, max);
// Round it up.
- effectiveMax = ((int) (max / rounding)) * rounding + rounding;
+ effectiveMax = ((int) (max / interval)) * interval + interval;
// Round it down.
- effectiveMin = ((int) (min / rounding)) * rounding;
+ effectiveMin = ((int) (min / interval)) * interval;
if (min < 0) {
- effectiveMin -= rounding;
+ effectiveMin -= interval;
}
- spread = getMax() - effectiveMin;
+ spread = effectiveMax - effectiveMin;
}
/**
@@ -168,16 +236,10 @@ public class ChartValueSeries {
}
/**
- * @return The minimum of the rounded up max value and the effective max.
+ * @return The rounded up maximum value
*/
public int getMax() {
- // Return the effective max if no absolute is set
- // or the effective max is less than the absolute.
- if ((absoluteMax == Integer.MIN_VALUE) || (effectiveMax < absoluteMax)) {
- return effectiveMax;
- } else {
- return absoluteMax;
- }
+ return effectiveMax;
}
/**
@@ -205,9 +267,7 @@ public class ChartValueSeries {
* @return The paint for this series
*/
Paint getPaint() {
- return (strokePaint == null)
- ? fillPaint
- : strokePaint;
+ return strokePaint == null ? fillPaint : strokePaint;
}
public Paint getLabelPaint() {
@@ -239,14 +299,6 @@ public class ChartValueSeries {
this.enabled = enabled;
}
- public int getAbsoluteMax() {
- return absoluteMax;
- }
-
- public void setAbsoluteMax(int absoluteMax) {
- this.absoluteMax = absoluteMax;
- }
-
public boolean hasData() {
return monitor.hasData();
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartView.java b/MyTracks/src/com/google/android/apps/mytracks/ChartView.java
index f72a521fd..948b5439d 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartView.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartView.java
@@ -15,6 +15,7 @@
*/
package com.google.android.apps.mytracks;
+import com.google.android.apps.mytracks.ChartValueSeries.ZoomSettings;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.ExtremityMonitor;
import com.google.android.apps.mytracks.util.StringUtils;
@@ -25,9 +26,11 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.DashPathEffect;
import android.graphics.Paint;
-import android.graphics.Paint.Style;
import android.graphics.Path;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -60,8 +63,10 @@ public class ChartView extends View {
*/
private int zoomLevel = 1;
private final int minZoomLevel = 1;
- private int maxZoomLevel = 5;
+ private int maxZoomLevel = 10;
+ private static final int MAX_INTERVALS = 5;
+
/*
* Borders, margins, dimensions (in pixels):
*/
@@ -80,7 +85,7 @@ public class ChartView extends View {
/**
* Unscaled bottom border of the chart.
*/
- private static final float BOTTOM_BORDER = 100;
+ private static final float BOTTOM_BORDER = 40;
/**
* Device scaled bottom border of the chart.
@@ -122,6 +127,7 @@ public class ChartView extends View {
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;
@@ -142,8 +148,6 @@ public class ChartView extends View {
private boolean metricUnits = true;
private boolean showPointer = false;
- private boolean yLabelMask[] = null;
-
/** Display chart versus distance or time */
public enum Mode {
BY_DISTANCE, BY_TIME
@@ -168,6 +172,9 @@ public class ChartView extends View {
gridPaint.setColor(context.getResources().getColor(R.color.gray));
gridPaint.setAntiAlias(false);
+ gridBarPaint.set(gridPaint);
+ gridBarPaint.setPathEffect(new DashPathEffect(new float[] {3, 2}, 0));
+
clearPaint.setStyle(Style.FILL);
clearPaint.setColor(context.getResources().getColor(R.color.white));
clearPaint.setAntiAlias(false);
@@ -193,43 +200,46 @@ public class ChartView extends View {
public void setUpChartValueSeries(Context context) {
series = new ChartValueSeries[NUM_SERIES];
- // Create the value series
+ // Create the value series.
series[ELEVATION_SERIES] =
new ChartValueSeries(context,
"###,###",
R.color.elevation_fill,
R.color.elevation_border,
- 100,
+ new ZoomSettings(MAX_INTERVALS,
+ new int[] {5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000}),
R.string.elevation);
series[SPEED_SERIES] =
new ChartValueSeries(context,
- "###,###.0",
- R.color.blue_transparent,
- R.color.blue,
- 5,
+ "###,###",
+ R.color.speed_fill,
+ R.color.speed_border,
+ new ZoomSettings(MAX_INTERVALS, 0, Integer.MIN_VALUE,
+ new int[] {5, 10, 20, 50}),
R.string.speed);
series[POWER_SERIES] =
new ChartValueSeries(context,
"###,###",
R.color.power_fill,
R.color.power_border,
- 5,
+ new ZoomSettings(MAX_INTERVALS, 0, 1000, new int[] {5, 50, 100, 200}),
R.string.power);
- series[POWER_SERIES].setAbsoluteMax(1500);
series[CADENCE_SERIES] =
new ChartValueSeries(context,
"###,###",
R.color.cadence_fill,
R.color.cadence_border,
- 5,
+ new ZoomSettings(MAX_INTERVALS, 0, Integer.MIN_VALUE,
+ new int[] {5, 10, 25, 50}),
R.string.cadence);
series[HEART_RATE_SERIES] =
new ChartValueSeries(context,
"###,###",
R.color.heartrate_fill,
R.color.heartrate_border,
- 5,
+ new ZoomSettings(MAX_INTERVALS, 0, Integer.MIN_VALUE,
+ new int[] {25, 50}),
R.string.heart_rate);
}
@@ -295,7 +305,7 @@ public class ChartView extends View {
data.add(theData);
addDataPointInternal(theData);
updateDimensions();
- setupPath();
+ setUpPath();
}
private void addDataPointInternal(double[] theData) {
@@ -326,7 +336,7 @@ public class ChartView extends View {
addDataPointInternal(d);
}
updateDimensions();
- setupPath();
+ setUpPath();
}
/**
@@ -360,7 +370,7 @@ public class ChartView extends View {
public void zoomIn() {
if (canZoomIn()) {
zoomLevel++;
- setupPath();
+ setUpPath();
invalidate();
}
}
@@ -377,7 +387,7 @@ public class ChartView extends View {
scrollX = effectiveWidth * (zoomLevel - 1);
scrollTo(scrollX, 0);
}
- setupPath();
+ setUpPath();
invalidate();
}
}
@@ -543,12 +553,12 @@ public class ChartView extends View {
@Override
protected synchronized void onDraw(Canvas c) {
if (w != c.getWidth() || h != c.getHeight()) {
- // Dimensions have changed (for example due to orientation change):
+ // Dimensions have changed (for example due to orientation change).
w = c.getWidth();
h = c.getHeight();
effectiveWidth = Math.max(0, w - leftBorder - RIGHT_BORDER);
effectiveHeight = Math.max(0, h - topBorder - bottomBorder);
- setupPath();
+ setUpPath();
}
c.save();
c.drawColor(Color.WHITE);
@@ -558,16 +568,21 @@ public class ChartView extends View {
c.restore();
return;
}
+
+ c.save();
+ c.clipRect(leftBorder + 1 + getScrollX(), topBorder + 1,
+ w - RIGHT_BORDER + getScrollX() - 1, h - bottomBorder - 1);
- // Draw the data series
+ drawGrid(c);
+
+ // Draw the data series.
for (ChartValueSeries cvs : series) {
if (cvs.isEnabled() && cvs.hasData()) {
cvs.drawPath(c);
}
}
- drawGrid(c);
-
- // Draw the waypoints:
+
+ // Draw the waypoints.
for (int i = 1; i < waypoints.size(); i++) {
final Waypoint waypoint = waypoints.get(i);
if (waypoint.getLocation() == null) {
@@ -577,7 +592,7 @@ public class ChartView extends View {
final float x = getWaypointX(waypoint);
c.drawLine(x, h - bottomBorder, x, topBorder, gridPaint);
- c.translate(x - (markerWidth / 2), markerHeight);
+ c.translate(x - markerWidth / 2, markerHeight);
if (waypoints.get(i).getType() == Waypoint.TYPE_STATISTICS) {
statsMarker.draw(c);
} else {
@@ -586,24 +601,27 @@ public class ChartView extends View {
c.restore();
}
- // Draw the axis and labels:
+ c.restore();
+
+ // Draw the axis and labels.
drawXLabels(c);
drawXAxis(c);
drawSeriesTitles(c);
-
- yLabelMask = new boolean[effectiveHeight / FONT_HEIGHT + 1];
+
c.translate(getScrollX(), 0);
drawYAxis(c);
+ final int spacer = 5;
+ int x = leftBorder - spacer;
for (ChartValueSeries cvs : series) {
if (cvs.isEnabled() && cvs.hasData()) {
- drawYLabels(cvs, c);
+ x -= drawYLabels(cvs, c, x) + spacer;
}
}
c.restore();
- if (showPointer && data.size() > 0) {
+ if (showPointer && !data.isEmpty()) {
c.translate(getX(maxX) - pointer.getIntrinsicWidth() / 2,
- (getY(series[0], data.get(data.size() - 1)[1])
- - pointer.getIntrinsicHeight() / 2 - 12));
+ getY(series[0], data.get(data.size() - 1)[1])
+ - pointer.getIntrinsicHeight() / 2 - 12);
pointer.draw(c);
}
}
@@ -618,8 +636,8 @@ public class ChartView extends View {
int j = 0;
for (ChartValueSeries cvs : series) {
if (cvs.isEnabled() && cvs.hasData()) {
- int y = (int) (w * zoomLevel * ((double) (++j) / sections));
- c.drawText(cvs.getTitle(), y, topBorder, cvs.getLabelPaint());
+ int x = (int) (w * (double) ++j / sections) + getScrollX();
+ c.drawText(cvs.getTitle(), x, topBorder, cvs.getLabelPaint());
}
}
}
@@ -628,23 +646,30 @@ public class ChartView extends View {
* Sets up the path that is used to draw the histogram in onDraw(). The path
* needs to be updated any time after the data or histogram dimensions change.
*/
- private synchronized void setupPath() {
+ private synchronized void setUpPath() {
for (ChartValueSeries cvs : series) {
cvs.getPath().reset();
}
- if (data.size() < 1) {
+ if (data.isEmpty()) {
return;
}
// All of the data points to the respective series.
- for (int i = 0; i < data.size(); i++) {
+ // TODO: Come up with a better sampling than 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++) {
+ for (int j = 0; j < min; ++j) {
ChartValueSeries cvs = series[j];
Path path = cvs.getPath();
- path.lineTo(getX(d[0]), getY(cvs, d[j + 1]));
+ int x = getX(d[0]);
+ int y = getY(cvs, d[j + 1]);
+ if (i == 0) {
+ path.moveTo(x, y);
+ } else {
+ path.lineTo(x, y);
+ }
}
}
@@ -693,12 +718,15 @@ public class ChartView extends View {
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) {
- maxLength = Math.max(maxLength,
- cvs.getMaxLabelLength());
+ if (cvs.isEnabled() && cvs.hasData()) {
+ maxLength += cvs.getMaxLabelLength();
+ }
}
- leftBorder = 4 + 7 * maxLength;
+ leftBorder = 4 + 8 * maxLength;
effectiveWidth = w - leftBorder - RIGHT_BORDER;
float density = getContext().getResources().getDisplayMetrics().density;
bottomBorder = (int) (density * BOTTOM_BORDER);
@@ -716,23 +744,20 @@ public class ChartView extends View {
}
private void drawXLabels(Canvas c) {
- int numLabels = 0;
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 {
- if (interval < 5) {
- interval = 2;
- } else if (interval < 10) {
- interval = 5;
- } else {
- interval = (interval / 10) * 10;
- }
+ interval = (interval / 10) * 10;
}
drawXLabel(c, 0, shortFormat);
- numLabels++;
+ int numLabels = 1;
for (int i = 1; i * interval < maxX; i++) {
drawXLabel(c, i * interval, shortFormat);
numLabels++;
@@ -742,26 +767,19 @@ public class ChartView extends View {
}
}
- private void drawYLabels(ChartValueSeries cvs, Canvas c) {
- int numLabels = 0;
- drawYLabel(cvs, c, 0);
- final int y0 = getY(cvs, 0);
- numLabels++;
+ private float drawYLabels(ChartValueSeries cvs, Canvas c, int x) {
int interval = cvs.getInterval();
- for (int i = 0; i * interval < cvs.getSpread(); i++) {
- final int y = i * interval + cvs.getMin();
- final int yi = getY(cvs, y);
- if (Math.abs(y0 - yi) > 15) {
- drawYLabel(cvs, c, y);
- numLabels++;
- }
- }
- if (numLabels < 3) {
- drawYLabel(cvs, c, cvs.getMax());
+ float maxTextWidth = 0;
+ for (int i = 0; i < MAX_INTERVALS; ++i) {
+ maxTextWidth = Math.max(maxTextWidth, drawYLabel(cvs, c, x, i * interval + cvs.getMin()));
}
+ return maxTextWidth;
}
private void drawXLabel(Canvas c, double x, boolean shortFormat) {
+ if (x < 0) {
+ return;
+ }
String s =
(mode == Mode.BY_DISTANCE)
? (shortFormat ? xShortFormat.format(x) : xFormat.format(x))
@@ -772,72 +790,55 @@ public class ChartView extends View {
labelPaint);
}
- private void drawYLabel(ChartValueSeries cvs, Canvas c, int y) {
- int desiredY =
- (int) ((y - cvs.getMin()) * effectiveHeight / cvs.getSpread());
-
- // Make sure we don't write one label on top of another.
- int slot = desiredY / FONT_HEIGHT;
- if (slot < 0) {
- return;
- }
- if (yLabelMask[slot]) {
- desiredY = ++slot * FONT_HEIGHT;
- }
- desiredY = topBorder + effectiveHeight + 5 - desiredY;
- if (slot < yLabelMask.length) {
- yLabelMask[slot] = true;
- }
- c.drawText(cvs.getFormat().format(y),
- 2, desiredY,
- cvs.getLabelPaint());
+ private float drawYLabel(ChartValueSeries cvs, Canvas c, int x, int y) {
+ int desiredY = (int) ((y - cvs.getMin()) * effectiveHeight /
+ (cvs.getInterval() * MAX_INTERVALS));
+ desiredY = topBorder + effectiveHeight + FONT_HEIGHT / 2 - desiredY - 1;
+ Paint p = new Paint(cvs.getLabelPaint());
+ p.setTextAlign(Align.RIGHT);
+ String text = cvs.getFormat().format(y);
+ c.drawText(text, x, desiredY, p);
+ return p.measureText(text);
}
private void drawXAxis(Canvas canvas) {
float rightEdge = getX(maxX);
- canvas.drawLine(leftBorder, effectiveHeight + topBorder, rightEdge,
- effectiveHeight + topBorder, borderPaint);
+ final int y = effectiveHeight + topBorder;
+ canvas.drawLine(leftBorder, y, rightEdge, y, borderPaint);
Context c = getContext();
- String s =
- (mode == Mode.BY_DISTANCE)
- ? (metricUnits ? c.getString(R.string.kilometer)
- : c.getString(R.string.mile)) : (c.getString(R.string.min));
- canvas.drawText(s,
- rightEdge,
- effectiveHeight + .2f * UNIT_BORDER + topBorder,
- labelPaint);
+ String s = mode == Mode.BY_DISTANCE
+ ? (metricUnits ? c.getString(R.string.kilometer) : c.getString(R.string.mile))
+ : c.getString(R.string.min);
+ canvas.drawText(s, rightEdge, effectiveHeight + .2f * UNIT_BORDER + topBorder, labelPaint);
}
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);
- canvas.drawRect(0, 0,
- leftBorder - 1, effectiveHeight + topBorder + 1,
- clearPaint);
+ for (int i = 1; i < MAX_INTERVALS; ++i) {
+ int y = i * effectiveHeight / MAX_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.meter)
- : c.getString(R.string.feet);
- canvas.drawText(s, leftBorder - UNIT_BORDER * .2f,
- UNIT_BORDER * .8f + topBorder, labelPaint);
+ String s = metricUnits ? c.getString(R.string.meter) : c.getString(R.string.feet);
+ canvas.drawText(s, leftBorder - UNIT_BORDER * .2f, UNIT_BORDER * .8f + topBorder, labelPaint);
}
-
+
private synchronized void drawGrid(Canvas c) {
- if (data.size() < 1) {
+ if (data.isEmpty()) {
return;
}
- for (ChartValueSeries cvs : series) {
- if (cvs.isEnabled()) {
- int interval = cvs.getInterval();
- for (int i = 1; i * interval < cvs.getSpread(); i++) {
- int y = getY(cvs, i * interval + cvs.getMin());
- c.drawLine(getX(data.get(0)[0]), y,
- getX(data.get(data.size() - 1)[0]), y,
- cvs.getPaint());
- }
- }
+
+ float rightEdge = getX(maxX);
+ for (int i = 1; i < MAX_INTERVALS; ++i) {
+ int y = i * effectiveHeight / MAX_INTERVALS + topBorder;
+ c.drawLine(leftBorder, y, rightEdge, y, gridBarPaint);
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriter.java
index cb49c4a51..2f9555023 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriter.java
@@ -16,9 +16,11 @@
package com.google.android.apps.mytracks.io;
import com.google.android.apps.mytracks.MyTracksConstants;
+import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
+import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.maps.mytracks.R;
@@ -266,62 +268,80 @@ public class TrackWriter {
private void writeLocations() {
boolean wroteFirst = false;
boolean segmentOpen = false;
- Location lastLoc = null, loc = null;
boolean isLastValid = false;
- Cursor locationsCursor =
- providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
- if (locationsCursor == null || !locationsCursor.moveToFirst()) {
- Log.w(MyTracksConstants.TAG, "Unable to get any points to write");
- return;
- }
-
- do {
- if (loc == null) loc = new Location("");
- providerUtils.fillLocation(locationsCursor, loc);
-
- boolean isValid = MyTracksUtils.isValidLocation(loc);
- boolean validSegment = isValid && isLastValid;
- if (!wroteFirst && validSegment) {
- // Found the first two consecutive points which are valid
- writer.writeBeginTrack(lastLoc);
- wroteFirst = true;
- }
-
- if (validSegment) {
- if (!segmentOpen) {
- // Start a segment for this point
- writer.writeOpenSegment();
- segmentOpen = true;
-
- // Write the previous point, which we had previously skipped
- writer.writeLocation(lastLoc);
+ class TrackWriterLocationFactory implements MyTracksProviderUtils.LocationFactory {
+ Location currentLocation;
+ Location lastLocation;
+
+ @Override
+ public Location createLocation() {
+ if (currentLocation == null) {
+ currentLocation = new MyTracksLocation("");
}
-
- // Write the current point
- writer.writeLocation(loc);
- } else {
- if (segmentOpen) {
- writer.writeCloseSegment();
- segmentOpen = false;
+ return currentLocation;
+ }
+
+ public void swapLocations() {
+ Location tmpLoc = lastLocation;
+ lastLocation = currentLocation;
+ currentLocation = tmpLoc;
+ if (currentLocation != null) {
+ currentLocation.reset();
}
}
-
- // Swap loc and lastLoc (so lastLoc is reused)
- Location tmp = lastLoc;
- lastLoc = loc;
- loc = tmp;
- if (loc != null) loc.reset();
-
- isLastValid = isValid;
- } while (locationsCursor.moveToNext());
-
- if (segmentOpen) {
- writer.writeCloseSegment();
- segmentOpen = false;
- }
- if (wroteFirst) {
- writer.writeEndTrack(lastLoc);
+ };
+
+ TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
+ LocationIterator it = providerUtils.getLocationIterator(track.getId(), 0, false,
+ locationFactory);
+ try {
+ if (!it.hasNext()) {
+ Log.w(MyTracksConstants.TAG, "Unable to get any points to write");
+ return;
+ }
+ while (it.hasNext()) {
+ Location loc = it.next();
+
+ boolean isValid = MyTracksUtils.isValidLocation(loc);
+ boolean validSegment = isValid && isLastValid;
+ if (!wroteFirst && validSegment) {
+ // Found the first two consecutive points which are valid
+ writer.writeBeginTrack(locationFactory.lastLocation);
+ wroteFirst = true;
+ }
+
+ if (validSegment) {
+ if (!segmentOpen) {
+ // Start a segment for this point
+ writer.writeOpenSegment();
+ segmentOpen = true;
+
+ // Write the previous point, which we had previously skipped
+ writer.writeLocation(locationFactory.lastLocation);
+ }
+
+ // Write the current point
+ writer.writeLocation(loc);
+ } else {
+ if (segmentOpen) {
+ writer.writeCloseSegment();
+ segmentOpen = false;
+ }
+ }
+
+ locationFactory.swapLocations();
+ isLastValid = isValid;
+ }
+ if (segmentOpen) {
+ writer.writeCloseSegment();
+ segmentOpen = false;
+ }
+ if (wroteFirst) {
+ writer.writeEndTrack(locationFactory.lastLocation);
+ }
+ } finally {
+ it.close();
}
}
}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/ChartValueSeriesTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/ChartValueSeriesTest.java
index e1ff590aa..491f181bc 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/ChartValueSeriesTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/ChartValueSeriesTest.java
@@ -15,6 +15,7 @@
*/
package com.google.android.apps.mytracks;
+import com.google.android.apps.mytracks.ChartValueSeries.ZoomSettings;
import com.google.android.maps.mytracks.R;
import android.graphics.Paint.Style;
@@ -32,12 +33,12 @@ public class ChartValueSeriesTest extends AndroidTestCase {
"###,###",
R.color.elevation_fill,
R.color.elevation_border,
- 100,
+ new ZoomSettings(5, new int[] {100}),
R.string.elevation);
}
public void testInitialConditions() {
- assertEquals(25, series.getInterval());
+ assertEquals(0, series.getInterval());
assertEquals(1, series.getMaxLabelLength());
assertEquals(0, series.getMin());
assertEquals(0, series.getMax());
@@ -57,7 +58,7 @@ public class ChartValueSeriesTest extends AndroidTestCase {
series.update(0);
series.update(10);
series.updateDimension();
- assertEquals(25, series.getInterval());
+ assertEquals(100, series.getInterval());
assertEquals(3, series.getMaxLabelLength());
assertEquals(0, series.getMin());
assertEquals(100, series.getMax());
@@ -68,7 +69,7 @@ public class ChartValueSeriesTest extends AndroidTestCase {
series.update(0);
series.update(901);
series.updateDimension();
- assertEquals(200, series.getInterval());
+ assertEquals(100, series.getInterval());
assertEquals(5, series.getMaxLabelLength());
assertEquals(0, series.getMin());
assertEquals(1000, series.getMax());
@@ -79,10 +80,60 @@ public class ChartValueSeriesTest extends AndroidTestCase {
series.update(500);
series.update(1401);
series.updateDimension();
- assertEquals(200, series.getInterval());
+ assertEquals(100, series.getInterval());
assertEquals(5, series.getMaxLabelLength());
assertEquals(500, series.getMin());
assertEquals(1500, series.getMax());
assertEquals(1000.0, series.getSpread());
}
+
+ public void testZoomSettings_invalidArgs() {
+ try {
+ new ZoomSettings(0, new int[] {10, 50, 100});
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // OK.
+ }
+ try {
+ new ZoomSettings(1, null);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // OK.
+ }
+ try {
+ new ZoomSettings(1, new int[] {});
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // OK.
+ }
+ try {
+ new ZoomSettings(1, new int[] {1, 3, 2});
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // OK.
+ }
+ }
+
+ public void testZoomSettings_minAligned() {
+ ZoomSettings settings = new ZoomSettings(5, new int[] {10, 50, 100});
+ assertEquals(10, settings.calculateInterval(0, 15));
+ assertEquals(10, settings.calculateInterval(0, 50));
+ assertEquals(50, settings.calculateInterval(0, 111));
+ assertEquals(50, settings.calculateInterval(0, 250));
+ assertEquals(100, settings.calculateInterval(0, 251));
+ assertEquals(100, settings.calculateInterval(0, 10000));
+ }
+
+ public void testZoomSettings_minNotAligned() {
+ ZoomSettings settings = new ZoomSettings(5, new int[] {10, 50, 100});
+ assertEquals(50, settings.calculateInterval(5, 55));
+ assertEquals(10, settings.calculateInterval(10, 60));
+ assertEquals(50, settings.calculateInterval(7, 250));
+ assertEquals(100, settings.calculateInterval(7, 257));
+ assertEquals(100, settings.calculateInterval(11, 10000));
+
+ // A regression test.
+ settings = new ZoomSettings(5, new int[] {5, 10, 20});
+ assertEquals(10, settings.calculateInterval(-37.14, -11.89));
+ }
}