forked from upstream-mirrors/OpenTracks
Chart drawing refactoring, part 1. Better handle label drawings for multiple data series, fix a few bugs and introduce fine grained zoom settings.
This commit is contained in:
@@ -30,13 +30,15 @@ limitations under the License.
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
|
android:padding="10dip"
|
||||||
android:layout_weight="0">
|
android:layout_weight="0">
|
||||||
<ZoomControls android:id="@+id/elevation_zoom"
|
<ZoomControls android:id="@+id/elevation_zoom"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_centerHorizontal="true"/>
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_alignParentRight="true"/>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
<LinearLayout android:id="@+id/elevation_busypane"
|
<LinearLayout android:id="@+id/elevation_busypane"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
|||||||
@@ -38,17 +38,96 @@ public class ChartValueSeries {
|
|||||||
private final Paint fillPaint;
|
private final Paint fillPaint;
|
||||||
private final Paint strokePaint;
|
private final Paint strokePaint;
|
||||||
private final Paint labelPaint;
|
private final Paint labelPaint;
|
||||||
private final int rounding;
|
private final ZoomSettings zoomSettings;
|
||||||
|
|
||||||
private String title;
|
private String title;
|
||||||
private double min = 0;
|
private double min;
|
||||||
private double max = 1;
|
private double max = 1.0;
|
||||||
private int effectiveMax = 0;
|
private int effectiveMax;
|
||||||
private int absoluteMax = Integer.MIN_VALUE;
|
private int effectiveMin;
|
||||||
private int effectiveMin = 0;
|
private double spread;
|
||||||
private double spread = 0;
|
private int interval;
|
||||||
private boolean enabled = true;
|
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 int getIntervals() {
|
||||||
|
return intervals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAbsoluteMin() {
|
||||||
|
return absoluteMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAbsoluteMax() {
|
||||||
|
return absoluteMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getZoomLevels() {
|
||||||
|
return 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* TODO: Use the absolute values.
|
||||||
|
*/
|
||||||
|
public int calculateInterval(double min, double max) {
|
||||||
|
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.
|
* 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 formatString The format of the decimal format for this series
|
||||||
* @param fill The paint for filling the chart
|
* @param fill The paint for filling the chart
|
||||||
* @param stroke The paint for stroking the outside the chart, optional
|
* @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,
|
public ChartValueSeries(Context context, String formatString, int fillColor, int strokeColor,
|
||||||
String formatString,
|
ZoomSettings zoomSettings, int titleId) {
|
||||||
int fillColor,
|
|
||||||
int strokeColor,
|
|
||||||
int rounding,
|
|
||||||
int titleId) {
|
|
||||||
this.format = new DecimalFormat(formatString);
|
this.format = new DecimalFormat(formatString);
|
||||||
fillPaint = new Paint();
|
fillPaint = new Paint();
|
||||||
fillPaint.setStyle(Style.FILL);
|
fillPaint.setStyle(Style.FILL);
|
||||||
@@ -81,7 +158,7 @@ public class ChartValueSeries {
|
|||||||
strokePaint = null;
|
strokePaint = null;
|
||||||
labelPaint = fillPaint;
|
labelPaint = fillPaint;
|
||||||
}
|
}
|
||||||
this.rounding = rounding;
|
this.zoomSettings = zoomSettings;
|
||||||
this.title = context.getString(titleId);
|
this.title = context.getString(titleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,20 +190,7 @@ public class ChartValueSeries {
|
|||||||
* @return The interval between markers
|
* @return The interval between markers
|
||||||
*/
|
*/
|
||||||
public int getInterval() {
|
public int getInterval() {
|
||||||
// Try to find 5 even looking intervals.
|
return interval;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -141,12 +205,14 @@ public class ChartValueSeries {
|
|||||||
min = monitor.getMin();
|
min = monitor.getMin();
|
||||||
max = monitor.getMax();
|
max = monitor.getMax();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.interval = zoomSettings.calculateInterval(min, max);
|
||||||
// Round it up.
|
// Round it up.
|
||||||
effectiveMax = ((int) (max / rounding)) * rounding + rounding;
|
effectiveMax = ((int) (max / interval)) * interval + interval;
|
||||||
// Round it down.
|
// Round it down.
|
||||||
effectiveMin = ((int) (min / rounding)) * rounding;
|
effectiveMin = ((int) (min / interval)) * interval;
|
||||||
if (min < 0) {
|
if (min < 0) {
|
||||||
effectiveMin -= rounding;
|
effectiveMin -= interval;
|
||||||
}
|
}
|
||||||
spread = getMax() - effectiveMin;
|
spread = getMax() - effectiveMin;
|
||||||
}
|
}
|
||||||
@@ -169,15 +235,14 @@ public class ChartValueSeries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The minimum of the rounded up max value and the effective max.
|
* @return The minimum of the rounded up max value and the effective max.
|
||||||
|
*
|
||||||
|
* TODO: Move to ZoomSettings.
|
||||||
*/
|
*/
|
||||||
public int getMax() {
|
public int getMax() {
|
||||||
// Return the effective max if no absolute is set
|
// Return the effective max if no absolute is set
|
||||||
// or the effective max is less than the absolute.
|
// or the effective max is less than the absolute.
|
||||||
if ((absoluteMax == Integer.MIN_VALUE) || (effectiveMax < absoluteMax)) {
|
return zoomSettings.absoluteMax == Integer.MIN_VALUE || effectiveMax < zoomSettings.absoluteMax
|
||||||
return effectiveMax;
|
? effectiveMax : zoomSettings.absoluteMax;
|
||||||
} else {
|
|
||||||
return absoluteMax;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -205,9 +270,7 @@ public class ChartValueSeries {
|
|||||||
* @return The paint for this series
|
* @return The paint for this series
|
||||||
*/
|
*/
|
||||||
Paint getPaint() {
|
Paint getPaint() {
|
||||||
return (strokePaint == null)
|
return strokePaint == null ? fillPaint : strokePaint;
|
||||||
? fillPaint
|
|
||||||
: strokePaint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Paint getLabelPaint() {
|
public Paint getLabelPaint() {
|
||||||
@@ -239,14 +302,6 @@ public class ChartValueSeries {
|
|||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAbsoluteMax() {
|
|
||||||
return absoluteMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAbsoluteMax(int absoluteMax) {
|
|
||||||
this.absoluteMax = absoluteMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasData() {
|
public boolean hasData() {
|
||||||
return monitor.hasData();
|
return monitor.hasData();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks;
|
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.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.stats.ExtremityMonitor;
|
import com.google.android.apps.mytracks.stats.ExtremityMonitor;
|
||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
import com.google.android.apps.mytracks.util.StringUtils;
|
||||||
@@ -25,9 +26,11 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.DashPathEffect;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Paint.Style;
|
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
|
import android.graphics.Paint.Align;
|
||||||
|
import android.graphics.Paint.Style;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.VelocityTracker;
|
import android.view.VelocityTracker;
|
||||||
@@ -60,7 +63,9 @@ public class ChartView extends View {
|
|||||||
*/
|
*/
|
||||||
private int zoomLevel = 1;
|
private int zoomLevel = 1;
|
||||||
private final int minZoomLevel = 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):
|
* Borders, margins, dimensions (in pixels):
|
||||||
@@ -80,7 +85,7 @@ public class ChartView extends View {
|
|||||||
/**
|
/**
|
||||||
* Unscaled bottom border of the chart.
|
* 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.
|
* 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 borderPaint = new Paint();
|
||||||
private final Paint labelPaint = new Paint();
|
private final Paint labelPaint = new Paint();
|
||||||
private final Paint gridPaint = new Paint();
|
private final Paint gridPaint = new Paint();
|
||||||
|
private final Paint gridBarPaint = new Paint();
|
||||||
private final Paint clearPaint = new Paint();
|
private final Paint clearPaint = new Paint();
|
||||||
private final Drawable pointer;
|
private final Drawable pointer;
|
||||||
private final Drawable statsMarker;
|
private final Drawable statsMarker;
|
||||||
@@ -142,8 +148,6 @@ public class ChartView extends View {
|
|||||||
private boolean metricUnits = true;
|
private boolean metricUnits = true;
|
||||||
private boolean showPointer = false;
|
private boolean showPointer = false;
|
||||||
|
|
||||||
private boolean yLabelMask[] = null;
|
|
||||||
|
|
||||||
/** Display chart versus distance or time */
|
/** Display chart versus distance or time */
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
BY_DISTANCE, BY_TIME
|
BY_DISTANCE, BY_TIME
|
||||||
@@ -168,6 +172,9 @@ public class ChartView extends View {
|
|||||||
gridPaint.setColor(context.getResources().getColor(R.color.gray));
|
gridPaint.setColor(context.getResources().getColor(R.color.gray));
|
||||||
gridPaint.setAntiAlias(false);
|
gridPaint.setAntiAlias(false);
|
||||||
|
|
||||||
|
gridBarPaint.set(gridPaint);
|
||||||
|
gridBarPaint.setPathEffect(new DashPathEffect(new float[] {3, 2}, 0));
|
||||||
|
|
||||||
clearPaint.setStyle(Style.FILL);
|
clearPaint.setStyle(Style.FILL);
|
||||||
clearPaint.setColor(context.getResources().getColor(R.color.white));
|
clearPaint.setColor(context.getResources().getColor(R.color.white));
|
||||||
clearPaint.setAntiAlias(false);
|
clearPaint.setAntiAlias(false);
|
||||||
@@ -193,43 +200,43 @@ public class ChartView extends View {
|
|||||||
public void setUpChartValueSeries(Context context) {
|
public void setUpChartValueSeries(Context context) {
|
||||||
series = new ChartValueSeries[NUM_SERIES];
|
series = new ChartValueSeries[NUM_SERIES];
|
||||||
|
|
||||||
// Create the value series
|
// Create the value series.
|
||||||
series[ELEVATION_SERIES] =
|
series[ELEVATION_SERIES] =
|
||||||
new ChartValueSeries(context,
|
new ChartValueSeries(context,
|
||||||
"###,###",
|
"###,###",
|
||||||
R.color.elevation_fill,
|
R.color.elevation_fill,
|
||||||
R.color.elevation_border,
|
R.color.elevation_border,
|
||||||
100,
|
new ZoomSettings(MAX_INTERVALS,
|
||||||
|
new int[] {5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000}),
|
||||||
R.string.elevation);
|
R.string.elevation);
|
||||||
|
|
||||||
series[SPEED_SERIES] =
|
series[SPEED_SERIES] =
|
||||||
new ChartValueSeries(context,
|
new ChartValueSeries(context,
|
||||||
"###,###.0",
|
"###,###",
|
||||||
R.color.blue_transparent,
|
R.color.blue_transparent,
|
||||||
R.color.blue,
|
R.color.blue,
|
||||||
5,
|
new ZoomSettings(MAX_INTERVALS, new int[] {5, 10, 20, 50}),
|
||||||
R.string.speed);
|
R.string.speed);
|
||||||
series[POWER_SERIES] =
|
series[POWER_SERIES] =
|
||||||
new ChartValueSeries(context,
|
new ChartValueSeries(context,
|
||||||
"###,###",
|
"###,###",
|
||||||
R.color.power_fill,
|
R.color.power_fill,
|
||||||
R.color.power_border,
|
R.color.power_border,
|
||||||
5,
|
new ZoomSettings(MAX_INTERVALS, 0, 1500, new int[] {5, 50, 100, 200}),
|
||||||
R.string.power);
|
R.string.power);
|
||||||
series[POWER_SERIES].setAbsoluteMax(1500);
|
|
||||||
series[CADENCE_SERIES] =
|
series[CADENCE_SERIES] =
|
||||||
new ChartValueSeries(context,
|
new ChartValueSeries(context,
|
||||||
"###,###",
|
"###,###",
|
||||||
R.color.cadence_fill,
|
R.color.cadence_fill,
|
||||||
R.color.cadence_border,
|
R.color.cadence_border,
|
||||||
5,
|
new ZoomSettings(MAX_INTERVALS, new int[] {5, 10, 25, 50}),
|
||||||
R.string.cadence);
|
R.string.cadence);
|
||||||
series[HEART_RATE_SERIES] =
|
series[HEART_RATE_SERIES] =
|
||||||
new ChartValueSeries(context,
|
new ChartValueSeries(context,
|
||||||
"###,###",
|
"###,###",
|
||||||
R.color.heartrate_fill,
|
R.color.heartrate_fill,
|
||||||
R.color.heartrate_border,
|
R.color.heartrate_border,
|
||||||
5,
|
new ZoomSettings(MAX_INTERVALS, new int[] {10, 50}),
|
||||||
R.string.heart_rate);
|
R.string.heart_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,7 +550,7 @@ public class ChartView extends View {
|
|||||||
@Override
|
@Override
|
||||||
protected synchronized void onDraw(Canvas c) {
|
protected synchronized void onDraw(Canvas c) {
|
||||||
if (w != c.getWidth() || h != c.getHeight()) {
|
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();
|
w = c.getWidth();
|
||||||
h = c.getHeight();
|
h = c.getHeight();
|
||||||
effectiveWidth = Math.max(0, w - leftBorder - RIGHT_BORDER);
|
effectiveWidth = Math.max(0, w - leftBorder - RIGHT_BORDER);
|
||||||
@@ -559,15 +566,20 @@ public class ChartView extends View {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the data series
|
c.save();
|
||||||
|
c.clipRect(leftBorder + 1 + getScrollX(), topBorder + 1,
|
||||||
|
w - RIGHT_BORDER + getScrollX() - 1, h - bottomBorder - 1);
|
||||||
|
|
||||||
|
drawGrid(c);
|
||||||
|
|
||||||
|
// Draw the data series.
|
||||||
for (ChartValueSeries cvs : series) {
|
for (ChartValueSeries cvs : series) {
|
||||||
if (cvs.isEnabled() && cvs.hasData()) {
|
if (cvs.isEnabled() && cvs.hasData()) {
|
||||||
cvs.drawPath(c);
|
cvs.drawPath(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawGrid(c);
|
|
||||||
|
|
||||||
// Draw the waypoints:
|
// Draw the waypoints.
|
||||||
for (int i = 1; i < waypoints.size(); i++) {
|
for (int i = 1; i < waypoints.size(); i++) {
|
||||||
final Waypoint waypoint = waypoints.get(i);
|
final Waypoint waypoint = waypoints.get(i);
|
||||||
if (waypoint.getLocation() == null) {
|
if (waypoint.getLocation() == null) {
|
||||||
@@ -586,18 +598,21 @@ public class ChartView extends View {
|
|||||||
c.restore();
|
c.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the axis and labels:
|
c.restore();
|
||||||
|
|
||||||
|
// Draw the axis and labels.
|
||||||
drawXLabels(c);
|
drawXLabels(c);
|
||||||
drawXAxis(c);
|
drawXAxis(c);
|
||||||
drawSeriesTitles(c);
|
drawSeriesTitles(c);
|
||||||
|
|
||||||
yLabelMask = new boolean[effectiveHeight / FONT_HEIGHT + 1];
|
|
||||||
c.translate(getScrollX(), 0);
|
c.translate(getScrollX(), 0);
|
||||||
drawYAxis(c);
|
drawYAxis(c);
|
||||||
|
int x = leftBorder - 5;
|
||||||
for (ChartValueSeries cvs : series) {
|
for (ChartValueSeries cvs : series) {
|
||||||
if (cvs.isEnabled() && cvs.hasData()) {
|
if (cvs.isEnabled() && cvs.hasData()) {
|
||||||
drawYLabels(cvs, c);
|
drawYLabels(cvs, c, x);
|
||||||
}
|
}
|
||||||
|
x -= 30;
|
||||||
}
|
}
|
||||||
c.restore();
|
c.restore();
|
||||||
if (showPointer && data.size() > 0) {
|
if (showPointer && data.size() > 0) {
|
||||||
@@ -618,8 +633,8 @@ public class ChartView extends View {
|
|||||||
int j = 0;
|
int j = 0;
|
||||||
for (ChartValueSeries cvs : series) {
|
for (ChartValueSeries cvs : series) {
|
||||||
if (cvs.isEnabled() && cvs.hasData()) {
|
if (cvs.isEnabled() && cvs.hasData()) {
|
||||||
int y = (int) (w * zoomLevel * ((double) (++j) / sections));
|
int x = (int) (w * (double) ++j / sections) + getScrollX();
|
||||||
c.drawText(cvs.getTitle(), y, topBorder, cvs.getLabelPaint());
|
c.drawText(cvs.getTitle(), x, topBorder, cvs.getLabelPaint());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -632,7 +647,7 @@ public class ChartView extends View {
|
|||||||
for (ChartValueSeries cvs : series) {
|
for (ChartValueSeries cvs : series) {
|
||||||
cvs.getPath().reset();
|
cvs.getPath().reset();
|
||||||
}
|
}
|
||||||
if (data.size() < 1) {
|
if (data.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,7 +659,11 @@ public class ChartView extends View {
|
|||||||
for (int j = 0; j < min; j++) {
|
for (int j = 0; j < min; j++) {
|
||||||
ChartValueSeries cvs = series[j];
|
ChartValueSeries cvs = series[j];
|
||||||
Path path = cvs.getPath();
|
Path path = cvs.getPath();
|
||||||
path.lineTo(getX(d[0]), getY(cvs, d[j + 1]));
|
if (i == 0) {
|
||||||
|
path.moveTo(getX(d[0]), getY(cvs, d[j + 1]));
|
||||||
|
} else {
|
||||||
|
path.lineTo(getX(d[0]), getY(cvs, d[j + 1]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,8 +714,7 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
int maxLength = 0;
|
int maxLength = 0;
|
||||||
for (ChartValueSeries cvs : series) {
|
for (ChartValueSeries cvs : series) {
|
||||||
maxLength = Math.max(maxLength,
|
maxLength += cvs.getMaxLabelLength();
|
||||||
cvs.getMaxLabelLength());
|
|
||||||
}
|
}
|
||||||
leftBorder = 4 + 7 * maxLength;
|
leftBorder = 4 + 7 * maxLength;
|
||||||
effectiveWidth = w - leftBorder - RIGHT_BORDER;
|
effectiveWidth = w - leftBorder - RIGHT_BORDER;
|
||||||
@@ -716,23 +734,20 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void drawXLabels(Canvas c) {
|
private void drawXLabels(Canvas c) {
|
||||||
int numLabels = 0;
|
|
||||||
double interval = (int) (maxX / zoomLevel / 4);
|
double interval = (int) (maxX / zoomLevel / 4);
|
||||||
boolean shortFormat = false;
|
boolean shortFormat = false;
|
||||||
if (interval < 1) {
|
if (interval < 1) {
|
||||||
interval = .5;
|
interval = .5;
|
||||||
shortFormat = true;
|
shortFormat = true;
|
||||||
|
} else if (interval < 5) {
|
||||||
|
interval = 2;
|
||||||
|
} else if (interval < 10) {
|
||||||
|
interval = 5;
|
||||||
} else {
|
} else {
|
||||||
if (interval < 5) {
|
interval = (interval / 10) * 10;
|
||||||
interval = 2;
|
|
||||||
} else if (interval < 10) {
|
|
||||||
interval = 5;
|
|
||||||
} else {
|
|
||||||
interval = (interval / 10) * 10;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
drawXLabel(c, 0, shortFormat);
|
drawXLabel(c, 0, shortFormat);
|
||||||
numLabels++;
|
int numLabels = 1;
|
||||||
for (int i = 1; i * interval < maxX; i++) {
|
for (int i = 1; i * interval < maxX; i++) {
|
||||||
drawXLabel(c, i * interval, shortFormat);
|
drawXLabel(c, i * interval, shortFormat);
|
||||||
numLabels++;
|
numLabels++;
|
||||||
@@ -742,26 +757,17 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawYLabels(ChartValueSeries cvs, Canvas c) {
|
private void drawYLabels(ChartValueSeries cvs, Canvas c, int x) {
|
||||||
int numLabels = 0;
|
|
||||||
drawYLabel(cvs, c, 0);
|
|
||||||
final int y0 = getY(cvs, 0);
|
|
||||||
numLabels++;
|
|
||||||
int interval = cvs.getInterval();
|
int interval = cvs.getInterval();
|
||||||
for (int i = 0; i * interval < cvs.getSpread(); i++) {
|
for (int i = 0; i < MAX_INTERVALS; ++i) {
|
||||||
final int y = i * interval + cvs.getMin();
|
drawYLabel(cvs, c, x, 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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawXLabel(Canvas c, double x, boolean shortFormat) {
|
private void drawXLabel(Canvas c, double x, boolean shortFormat) {
|
||||||
|
if (x < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
String s =
|
String s =
|
||||||
(mode == Mode.BY_DISTANCE)
|
(mode == Mode.BY_DISTANCE)
|
||||||
? (shortFormat ? xShortFormat.format(x) : xFormat.format(x))
|
? (shortFormat ? xShortFormat.format(x) : xFormat.format(x))
|
||||||
@@ -772,36 +778,23 @@ public class ChartView extends View {
|
|||||||
labelPaint);
|
labelPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawYLabel(ChartValueSeries cvs, Canvas c, int y) {
|
private void drawYLabel(ChartValueSeries cvs, Canvas c, int x, int y) {
|
||||||
int desiredY =
|
int desiredY = (int) ((y - cvs.getMin()) * effectiveHeight /
|
||||||
(int) ((y - cvs.getMin()) * effectiveHeight / cvs.getSpread());
|
(cvs.getInterval() * MAX_INTERVALS));
|
||||||
|
desiredY = topBorder + effectiveHeight + FONT_HEIGHT / 2 - desiredY;
|
||||||
// Make sure we don't write one label on top of another.
|
Paint p = new Paint(cvs.getLabelPaint());
|
||||||
int slot = desiredY / FONT_HEIGHT;
|
p.setTextAlign(Align.RIGHT);
|
||||||
if (slot < 0) {
|
c.drawText(cvs.getFormat().format(y), x, desiredY, p);
|
||||||
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 void drawXAxis(Canvas canvas) {
|
private void drawXAxis(Canvas canvas) {
|
||||||
float rightEdge = getX(maxX);
|
float rightEdge = getX(maxX);
|
||||||
canvas.drawLine(leftBorder, effectiveHeight + topBorder, rightEdge,
|
final int y = effectiveHeight + topBorder;
|
||||||
effectiveHeight + topBorder, borderPaint);
|
canvas.drawLine(leftBorder, y, rightEdge, y, borderPaint);
|
||||||
Context c = getContext();
|
Context c = getContext();
|
||||||
String s =
|
String s = (mode == Mode.BY_DISTANCE)
|
||||||
(mode == Mode.BY_DISTANCE)
|
? (metricUnits ? c.getString(R.string.kilometer)
|
||||||
? (metricUnits ? c.getString(R.string.kilometer)
|
: c.getString(R.string.mile)) : (c.getString(R.string.min));
|
||||||
: c.getString(R.string.mile)) : (c.getString(R.string.min));
|
|
||||||
canvas.drawText(s,
|
canvas.drawText(s,
|
||||||
rightEdge,
|
rightEdge,
|
||||||
effectiveHeight + .2f * UNIT_BORDER + topBorder,
|
effectiveHeight + .2f * UNIT_BORDER + topBorder,
|
||||||
@@ -809,12 +802,18 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void drawYAxis(Canvas canvas) {
|
private void drawYAxis(Canvas canvas) {
|
||||||
|
canvas.drawRect(0, 0,
|
||||||
|
leftBorder - 1, effectiveHeight + topBorder + UNIT_BORDER + 1,
|
||||||
|
clearPaint);
|
||||||
canvas.drawLine(leftBorder, UNIT_BORDER + topBorder,
|
canvas.drawLine(leftBorder, UNIT_BORDER + topBorder,
|
||||||
leftBorder, effectiveHeight + topBorder,
|
leftBorder, effectiveHeight + topBorder,
|
||||||
borderPaint);
|
borderPaint);
|
||||||
canvas.drawRect(0, 0,
|
final int intervals = getIntervals();
|
||||||
leftBorder - 1, effectiveHeight + topBorder + 1,
|
for (int i = 1; i < intervals; ++i) {
|
||||||
clearPaint);
|
int y = i * effectiveHeight / intervals + topBorder;
|
||||||
|
canvas.drawLine(leftBorder - 5, y, leftBorder, y, gridPaint);
|
||||||
|
}
|
||||||
|
|
||||||
Context c = getContext();
|
Context c = getContext();
|
||||||
// TODO: This should really show units for all series.
|
// TODO: This should really show units for all series.
|
||||||
String s = metricUnits
|
String s = metricUnits
|
||||||
@@ -824,20 +823,20 @@ public class ChartView extends View {
|
|||||||
UNIT_BORDER * .8f + topBorder, labelPaint);
|
UNIT_BORDER * .8f + topBorder, labelPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getIntervals() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized void drawGrid(Canvas c) {
|
private synchronized void drawGrid(Canvas c) {
|
||||||
if (data.size() < 1) {
|
if (data.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (ChartValueSeries cvs : series) {
|
|
||||||
if (cvs.isEnabled()) {
|
float rightEdge = getX(maxX);
|
||||||
int interval = cvs.getInterval();
|
final int intervals = getIntervals();
|
||||||
for (int i = 1; i * interval < cvs.getSpread(); i++) {
|
for (int i = 1; i < intervals; ++i) {
|
||||||
int y = getY(cvs, i * interval + cvs.getMin());
|
int y = i * effectiveHeight / intervals + topBorder;
|
||||||
c.drawLine(getX(data.get(0)[0]), y,
|
c.drawLine(leftBorder, y, rightEdge, y, gridBarPaint);
|
||||||
getX(data.get(data.size() - 1)[0]), y,
|
|
||||||
cvs.getPaint());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.ChartValueSeries.ZoomSettings;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Paint.Style;
|
import android.graphics.Paint.Style;
|
||||||
import android.test.AndroidTestCase;
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ public class ChartValueSeriesTest extends AndroidTestCase {
|
|||||||
"###,###",
|
"###,###",
|
||||||
R.color.elevation_fill,
|
R.color.elevation_fill,
|
||||||
R.color.elevation_border,
|
R.color.elevation_border,
|
||||||
100,
|
new ZoomSettings(5, new int[] {100}),
|
||||||
R.string.elevation);
|
R.string.elevation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,4 +86,54 @@ public class ChartValueSeriesTest extends AndroidTestCase {
|
|||||||
assertEquals(1500, series.getMax());
|
assertEquals(1500, series.getMax());
|
||||||
assertEquals(1000.0, series.getSpread());
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user