diff --git a/.gitignore b/.gitignore
index d6201ac..c32f129 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ build/
.project
.classpath
.settings/
+*.iml
diff --git a/build.gradle b/build.gradle
index 63b01c1..f9af734 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,11 +4,11 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.6.3'
+ classpath 'com.android.tools.build:gradle:1.0.0'
}
}
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
apply plugin: 'eclipse'
repositories {
@@ -16,13 +16,21 @@ repositories {
}
dependencies {
- compile fileTree(dir: 'libs', include: '*.jar')
+ //compile fileTree(dir: 'libs', include: '*.jar')
+ compile 'com.android.support:support-v4:21.0.3'
+ compile 'com.dev-smart:devsmart-android:0.1.5'
compile project(':AndroidEssentials')
}
android {
- buildToolsVersion "19.0.0"
- compileSdkVersion 10
+ compileSdkVersion 19
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 14
+ }
+
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
new file mode 100644
index 0000000..4ed2628
--- /dev/null
+++ b/res/values/attrs.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/devsmart/plotter/AxisFunction.java b/src/com/devsmart/plotter/AxisFunction.java
new file mode 100644
index 0000000..b6a43ca
--- /dev/null
+++ b/src/com/devsmart/plotter/AxisFunction.java
@@ -0,0 +1,21 @@
+package com.devsmart.plotter;
+
+public interface AxisFunction {
+
+ void interpolate(float[] x, float[] y);
+
+ AxisFunction copy();
+
+ /**
+ * translate screen coord --> graph coord
+ * @param x
+ * @return
+ */
+ float value(float x);
+
+ /**
+ * return the inverse function
+ * @return
+ */
+ AxisFunction inverse();
+}
diff --git a/src/com/devsmart/plotter/AxisRenderer.java b/src/com/devsmart/plotter/AxisRenderer.java
index cd1bd45..0441c4c 100644
--- a/src/com/devsmart/plotter/AxisRenderer.java
+++ b/src/com/devsmart/plotter/AxisRenderer.java
@@ -1,10 +1,36 @@
package com.devsmart.plotter;
import android.graphics.Canvas;
+import android.graphics.Rect;
import android.graphics.RectF;
public interface AxisRenderer {
-
- void drawAxis(Canvas canvas, RectF viewport, GraphView graphview);
+ /**
+ * Measure the area where the graph data will be drawn. Return a Rect
+ * in screen coordinates.
+ *
+ * @param screenWidth
+ * @param screenHeight
+ * @return
+ */
+ Rect measureGraphArea(int screenWidth, int screenHeight);
-}
+ /**
+ * Draw the axis on the canvas.
+ *
+ * @param canvas
+ * @param canvasWidth
+ * @param canvasHeight
+ * @param viewport
+ * @return
+ */
+ void drawAxis(Canvas canvas, int canvasWidth, int canvasHeight, RectF viewport, CoordinateSystem coordSystem);
+
+ void setXAxisLabel(String label);
+
+ void setYAxisLabel(String label);
+
+ void setAxisColor(int color);
+
+ void setLabelColor(int color);
+}
\ No newline at end of file
diff --git a/src/com/devsmart/plotter/CoordinateSystem.java b/src/com/devsmart/plotter/CoordinateSystem.java
new file mode 100644
index 0000000..fb27a4d
--- /dev/null
+++ b/src/com/devsmart/plotter/CoordinateSystem.java
@@ -0,0 +1,95 @@
+package com.devsmart.plotter;
+
+import android.graphics.RectF;
+
+public class CoordinateSystem {
+
+ AxisFunction mXAxisFunction;
+ AxisFunction mYAxisFunction;
+ CoordinateSystem mInverse;
+
+
+
+ public CoordinateSystem copy() {
+ CoordinateSystem retval = new CoordinateSystem();
+ retval.mXAxisFunction = mXAxisFunction.copy();
+ retval.mYAxisFunction = mYAxisFunction.copy();
+
+ return retval;
+ }
+
+ public float xValue(float x) {
+ return mXAxisFunction.value(x);
+ }
+
+ public float yValue(float y) {
+ return mYAxisFunction.value(y);
+ }
+
+ public void mapRect(RectF dest, RectF src) {
+ dest.left = mXAxisFunction.value(src.left);
+ dest.right = mXAxisFunction.value(src.right);
+ dest.bottom = mYAxisFunction.value(src.bottom);
+ dest.top = mYAxisFunction.value(src.top);
+ }
+
+ public void mapRect(RectF rect) {
+ rect.left = mXAxisFunction.value(rect.left);
+ rect.right = mXAxisFunction.value(rect.right);
+ rect.bottom = mYAxisFunction.value(rect.bottom);
+ rect.top = mYAxisFunction.value(rect.top);
+
+ }
+
+ /**
+ * Apply this system to the array of 2D points, and write the transformed points back into the array
+ * @param pts
+ */
+ public void mapPoints(float[] pts) {
+ mapPoints(pts, pts);
+ /*
+ for(int x=0;x= startPix + pixelWidth) {
+
+ //min
+ points[0] = (float) startPix;
+ points[1] = (float) yMinMax[0];
+
+ //max
+ points[2] = (float) startPix;
+ points[3] = (float) yMinMax[1];
+
+ coordSystem.mapPoints(points);
+
+ p.lineTo(points[0], points[1]);
+ p.lineTo(points[2], points[3]);
+
+ //reset min max
+ yMinMax[0] = Double.MAX_VALUE;
+ yMinMax[1] = Double.MIN_VALUE;
+
+ startPix = x;
+ }
+ }
+
+ points[0] = viewPort.right;
+ points[1] = (float) mFunction.value(viewPort.right);
+ coordSystem.mapPoints(points);
+ p.lineTo(points[0], points[1]);
+
+ canvas.drawPath(p, mPointPaint);
+ }
+
+ private class MinMax {
+ double min;
+ double max;
+
+ public MinMax() {
+ reset();
+ }
+
+ public void reset() {
+ min = Double.MAX_VALUE;
+ max = Double.MIN_VALUE;
+ }
+
+ public void add(float value) {
+ min = Math.min(min, value);
+ max = Math.max(max, value);
+ }
+ }
+
+ private void drawAtSampleLocations(Canvas canvas, RectF viewPort, CoordinateSystem coordSystem) {
+
+ Paint pointPaint = new Paint();
+ pointPaint.setColor(Color.RED);
+
+ int sampleindex = 0;
+ for (int i = 0; i < mSampleLocations.length; i++) {
+ if (mSampleLocations[i] >= viewPort.left) {
+ sampleindex = i;
+ if (i > 0) {
+ sampleindex = i - 1;
+ }
+ break;
+ }
+ }
+
+ //reset min max
+ yMinMax[0] = Double.MAX_VALUE;
+ yMinMax[1] = Double.MIN_VALUE;
+ MinMax xminmax = new MinMax();
+ MinMax yminmax = new MinMax();
+
+ final double pixelWidth = viewPort.width() / (double) canvas.getWidth();
+
+ Path p = new Path();
+ Path p2 = new Path();
+
+ points[0] = (float) mSampleLocations[sampleindex];
+ points[1] = (float) mFunction.value(points[0]);
+ xminmax.add(points[0]);
+ yminmax.add(points[1]);
+
+ coordSystem.mapPoints(points);
+ p.lineTo(points[0], points[1]);
+ p2.moveTo(points[0], points[1]);
+ lastPoint[0] = points[0];
+ lastPoint[1] = points[1];
+
+ double startPix = mSampleLocations[sampleindex];
+ double x = 0;
+ while (true) {
+ if (sampleindex >= mSampleLocations.length - 1 || (x = mSampleLocations[sampleindex++ + 1]) > viewPort.right) {
+ break;
+ }
+
+ final double y = mFunction.value(x);
+
+
+ points[0] = (float) x;
+ points[1] = (float) y;
+ coordSystem.mapPoints(points);
+
+ canvas.drawCircle(points[0], points[1], 3.0f, pointPaint);
+
+ p.lineTo((lastPoint[0] + points[0]) / 2, lastPoint[1]);
+ p.lineTo((lastPoint[0] + points[0]) / 2, points[1]);
+
+ //p.lineTo(points[0], lastPoint[1]);
+ //p.lineTo(points[0], points[1]);
+ lastPoint[0] = points[0];
+ lastPoint[1] = points[1];
+
+ /*
+ yMinMax[0] = Math.min(yMinMax[0], y);
+ yMinMax[1] = Math.max(yMinMax[1], y);
+
+ if(x >= startPix+pixelWidth){
+
+ //min
+ points[0] = (float) x;
+ points[1] = (float) yMinMax[0];
+
+ //max
+ points[2] = (float) x;
+ points[3] = (float) yMinMax[1];
+
+ xminmax.add(points[0]);
+ yminmax.add(points[1]);
+ xminmax.add(points[2]);
+ yminmax.add(points[3]);
+
+ coordSystem.mapPoints(points);
+
+ p.quadTo((lastPoint[0]+points[0])/2, coordSystem.yValue((lastPoint[0]+points[0])/2), points[0], points[1]);
+ lastPoint[0] = points[0];
+ lastPoint[1] = points[1];
+
+ p.quadTo((lastPoint[0]+points[2])/2, coordSystem.yValue((lastPoint[0]+points[2])/2), points[2], points[3]);
+ lastPoint[0] = points[2];
+ lastPoint[1] = points[3];
+
+ //reset min max
+ yMinMax[0] = Double.MAX_VALUE;
+ yMinMax[1] = Double.MIN_VALUE;
+
+ startPix = x;
+ }
+ */
+ }
+
+ p.lineTo(canvas.getWidth(), lastPoint[1]);
+
+ points[0] = viewPort.right;
+ points[1] = (float) mFunction.value(viewPort.right);
+ xminmax.add(points[0]);
+ yminmax.add(points[1]);
+ coordSystem.mapPoints(points);
+ //p.lineTo(points[0], points[1]);
+ //p.quadTo((lastPoint[0] + points[0]) / 2, (lastPoint[1] + points[1]) / 2, points[0], points[1]);
+
+
+ p.lineTo(canvas.getWidth(), 0);
+
+ //p.lineTo(xminmax.max, yminmax.min);
+ //p.lineTo(xminmax.min, yminmax.min);
+
+
+ p.close();
+
+ canvas.drawPath(p, mPointPaint);
+
+
+ //reset min max
+ yMinMax[0] = Double.MAX_VALUE;
+ yMinMax[1] = Double.MIN_VALUE;
+ final double stepWidth = Math.min(pixelWidth, 1.0 / mSampleRate);
+ startPix = viewPort.left;
+ for (x = startPix; x < viewPort.right; x += stepWidth) {
+ final double y = mFunction.value(x);
+ yMinMax[0] = Math.min(yMinMax[0], y);
+ yMinMax[1] = Math.max(yMinMax[1], y);
+
+ if (x >= startPix + pixelWidth) {
+
+ //min
+ points[0] = (float) startPix;
+ points[1] = (float) yMinMax[0];
+
+ //max
+ points[2] = (float) startPix;
+ points[3] = (float) yMinMax[1];
+
+ coordSystem.mapPoints(points);
+
+ p2.lineTo(points[0], points[1]);
+ p2.lineTo(points[2], points[3]);
+
+ //reset min max
+ yMinMax[0] = Double.MAX_VALUE;
+ yMinMax[1] = Double.MIN_VALUE;
+
+ startPix = x;
+ }
+ }
+
+ Paint p2Paint = new Paint();
+ p2Paint.setStyle(Paint.Style.STROKE);
+ p2Paint.setColor(Color.WHITE);
+ p2Paint.setStrokeWidth(2.0f);
+ p2Paint.setAntiAlias(true);
+ canvas.drawPath(p2, p2Paint);
+ }
+
+ @Override
+ public void draw(Canvas canvas, RectF viewPort, CoordinateSystem coordSystem) {
+ if (mSampleLocations == null) {
+ drawFixedSample(canvas, viewPort, coordSystem);
+ } else {
+ drawAtSampleLocations(canvas, viewPort, coordSystem);
+ }
+ }
+
+ @Override
+ public void setPaintColor(int color) {
+ mPointPaint.setColor(color);
+ }
+}
\ No newline at end of file
diff --git a/src/com/devsmart/plotter/FunctionRenderer2.java b/src/com/devsmart/plotter/FunctionRenderer2.java
new file mode 100644
index 0000000..bc4e1f5
--- /dev/null
+++ b/src/com/devsmart/plotter/FunctionRenderer2.java
@@ -0,0 +1,60 @@
+package com.devsmart.plotter;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+
+public class FunctionRenderer2 implements DataRenderer {
+
+ public interface GraphFunction {
+ double value(double x);
+ }
+
+ private final GraphFunction mFunction;
+ protected final Paint mPaint = new Paint();
+
+
+ public FunctionRenderer2(GraphFunction f, int color) {
+ mFunction = f;
+ mPaint.setColor(color);
+ mPaint.setStrokeWidth(2.0f);
+ mPaint.setAntiAlias(true);
+ mPaint.setStyle(Paint.Style.STROKE);
+ }
+
+ public static boolean isRealNumber(float f) {
+ return !Float.isNaN(f) && !Float.isInfinite(f);
+ }
+
+ @Override
+ public void draw(Canvas canvas, RectF viewPort, CoordinateSystem coordSystem) {
+
+ float[] points = new float[2];
+ final double pixelWidth = viewPort.width() / (double) canvas.getWidth();
+
+ Path p = new Path();
+ for (double x = viewPort.left; x <= viewPort.right; x += pixelWidth) {
+ final double y = mFunction.value(x);
+
+ points[0] = (float) x;
+ points[1] = (float) y;
+
+ coordSystem.mapPoints(points);
+ if (isRealNumber(points[0]) && isRealNumber(points[1])) {
+ if (x == viewPort.left) {
+ p.moveTo(points[0], points[1]);
+ } else {
+ p.lineTo(points[0], points[1]);
+ }
+ }
+ }
+
+ canvas.drawPath(p, mPaint);
+ }
+
+ @Override
+ public void setPaintColor(int color) {
+ mPaint.setColor(color);
+ }
+}
\ No newline at end of file
diff --git a/src/com/devsmart/plotter/GraphView.java b/src/com/devsmart/plotter/GraphView.java
index 3d46cc5..7db3e61 100644
--- a/src/com/devsmart/plotter/GraphView.java
+++ b/src/com/devsmart/plotter/GraphView.java
@@ -1,16 +1,11 @@
package com.devsmart.plotter;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
-import android.graphics.Matrix.ScaleToFit;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -18,578 +13,373 @@
import android.os.Parcelable;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
-import android.widget.ZoomButtonsController;
-
-import com.devsmart.BackgroundTask;
-
-public abstract class GraphView extends View {
-
- public static enum Axis {
- X,
- Y
- }
-
- private static final String KEY_VIEWPORT = "viewport";
- private static final String KEY_SUPERINSTANCE = "superinstance";
-
- private ExecutorService mDrawThread = Executors.newSingleThreadExecutor();
-
- private RectF mViewPort = new RectF();
- protected LinkedList mSeries = new LinkedList();
-
- private Bitmap mFrontBuffer;
- private Matrix mTransformMatrix = new Matrix();
- private Paint mDrawPaint = new Paint();
- private BackgroundDrawTask mBackgroundDrawTask;
- private GestureDetector mPanGestureDetector;
- private XYScaleGestureDetector mScaleGestureDetector;
-
- //draw prefs
- protected boolean mDrawXAxis;
- protected boolean mDrawYAxis;
- protected int mAxisColor;
- protected Paint mAxisLabelPaint = new Paint();
- protected Rect mPlotMargins = new Rect();
- protected int mBackgroundColor;
- public float mXAxisDevision;
- public float mYAxisDevision;
- protected int mXAxisMargin;
- protected int mYAxisMargin;
-
- protected AxisRenderer mAxisRenderer = new SimpleAxisRenderer();
-
- private ZoomButtonsController mZoomControls;
-
- public GraphView(Context context) {
- super(context);
- init();
- }
-
- public GraphView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
-
- private void init() {
- mPanGestureDetector = new GestureDetector(mSimpleGestureListener);
- mScaleGestureDetector = new XYScaleGestureDetector(getContext(), mSimpleScaleGestureListener);
- mDrawPaint.setFilterBitmap(true);
- mViewPort.set(-1, -1, 1, 1);
- mTransformMatrix.reset();
-
- //defaults
- mDrawXAxis = true;
- mXAxisDevision = 1.0f;
- mDrawYAxis = true;
- mYAxisDevision = 1.0f;
- mPlotMargins.set(20, 0, 0, 20);
- mAxisColor = Color.DKGRAY;
- mAxisLabelPaint.setColor(Color.DKGRAY);
- mAxisLabelPaint.setTextSize(15.0f);
- mAxisLabelPaint.setAntiAlias(true);
- mBackgroundColor = Color.WHITE;
-
- mZoomControls = new ZoomButtonsController(this);
- mZoomControls.setAutoDismissed(true);
- mZoomControls.setOnZoomListener(mZoomButtonListener);
-
- }
-
-
-
- @Override
- protected Parcelable onSaveInstanceState() {
-
- Bundle retval = new Bundle();
- retval.putParcelable(KEY_SUPERINSTANCE, super.onSaveInstanceState());
-
- float[] viewportvalues = new float[4];
- viewportvalues[0] = mViewPort.left;
- viewportvalues[1] = mViewPort.top;
- viewportvalues[2] = mViewPort.right;
- viewportvalues[3] = mViewPort.bottom;
- retval.putFloatArray(KEY_VIEWPORT, viewportvalues);
- return retval;
- }
-
- protected void onRestoreInstanceState (Parcelable state) {
- Bundle bundle = (Bundle) state;
- super.onRestoreInstanceState(bundle.getParcelable(KEY_SUPERINSTANCE));
-
- float[] viewportvalues = bundle.getFloatArray(KEY_VIEWPORT);
- mViewPort.left = viewportvalues[0];
- mViewPort.top = viewportvalues[1];
- mViewPort.right = viewportvalues[2];
- mViewPort.bottom = viewportvalues[3];
- drawFrame(mViewPort);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mZoomControls.setVisible(false);
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- if(visibility != View.VISIBLE){
- mZoomControls.setVisible(false);
- }
- }
-
- public void addSeries(Series series) {
- mSeries.add(series);
- drawFrame(mViewPort);
- }
-
- public void removeSeries(Series series) {
- mSeries.remove(series);
- drawFrame(mViewPort);
- }
-
- public Matrix getViewportToScreenMatrix(
- RectF screen,
- RectF viewPort){
-
- Matrix matrix = new Matrix();
- matrix.setRectToRect(viewPort,
- screen,
- ScaleToFit.FILL);
-
- matrix.postScale(1, -1);
- matrix.postTranslate(0, screen.height());
-
- return matrix;
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- drawFrame(mViewPort);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
-
- mZoomControls.setVisible(true);
-
- final int action = MotionEventCompat.getActionMasked(event);
- switch(action){
- case MotionEvent.ACTION_UP:
- updateViewport();
- break;
- }
-
- boolean retval = mPanGestureDetector.onTouchEvent(event);
- retval |= mScaleGestureDetector.onTouchEvent(event);
- return retval;
- }
-
- protected void updateViewport(){
- RectF newViewport = getDisplayViewPort();
- drawFrame(newViewport);
- }
-
- public RectF getDisplayViewPort(){
- Matrix m = new Matrix();
- mTransformMatrix.invert(m);
-
- RectF screen = new RectF(0,0, getWidth(), getHeight());
- m.mapRect(screen);
-
- Matrix viewPortTransform = getViewportToScreenMatrix(new RectF(0,0,getWidth(), getHeight()), mViewPort);
- Matrix screenToViewPort = new Matrix();
- viewPortTransform.invert(screenToViewPort);
-
- screenToViewPort.mapRect(screen);
- return screen;
- }
-
- public void setDisplayViewPort(RectF viewport) {
- drawFrame(viewport);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- if(mFrontBuffer != null){
- canvas.drawBitmap(mFrontBuffer, mTransformMatrix, mDrawPaint);
- }
- mAxisRenderer.drawAxis(canvas, mViewPort, this);
- }
-
- protected abstract void drawGraph(Canvas canvas, RectF viewPort);
-
-
- private void drawFrame(final RectF viewport) {
- if(mBackgroundDrawTask != null){
- mBackgroundDrawTask.mCanceled = true;
- }
- mBackgroundDrawTask = new BackgroundDrawTask(getMeasuredWidth(), getMeasuredHeight(), new RectF(viewport));
- BackgroundTask.runBackgroundTask(mBackgroundDrawTask, mDrawThread);
- }
-
- private class BackgroundDrawTask extends BackgroundTask {
-
- private int width;
- private int height;
- private Bitmap mDrawBuffer;
- private boolean mCanceled = false;
- private final RectF viewport;
-
- public BackgroundDrawTask(int width, int height, RectF viewport){
- this.width = width;
- this.height = height;
- this.viewport = new RectF(viewport);
- }
-
- @Override
- public void onBackground() {
- if(!mCanceled){
- mDrawBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
- drawGraph(new Canvas(mDrawBuffer), viewport);
- }
- }
-
- @Override
- public void onAfter() {
- if(!mCanceled){
- mFrontBuffer = mDrawBuffer;
- mViewPort = viewport;
- mTransformMatrix.reset();
- invalidate();
- //mBackgroundDrawTask = null;
- } else if(mDrawBuffer != null) {
- mDrawBuffer.recycle();
- mDrawBuffer = null;
- }
- }
-
-
- }
-
- private GestureDetector.SimpleOnGestureListener mSimpleGestureListener = new GestureDetector.SimpleOnGestureListener(){
-
- @Override
- public boolean onDown(MotionEvent e) {
- return true;
- }
-
-
-
- @Override
- public boolean onDoubleTap(MotionEvent e) {
- //autoScaleDomainAndRange();
- mTransformMatrix.postScale(1.3f, 1.3f, e.getX(), e.getY());
- invalidate();
- updateViewport();
- return true;
- }
-
-
-
- @Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- mTransformMatrix.postTranslate(-distanceX, -distanceY);
- invalidate();
- updateViewport();
- return true;
- }
-
- };
-
- private XYScaleGestureDetector.SimpleOnScaleGestureListener mSimpleScaleGestureListener = new XYScaleGestureDetector.SimpleOnScaleGestureListener(){
-
- @Override
- public boolean onScale(XYScaleGestureDetector detector) {
- //float scale = detector.getScaleFactor();
-
- mTransformMatrix.postScale(detector.getXScaleFactor(), detector.getYScaleFactor(), detector.getFocusX(), detector.getFocusY());
- invalidate();
- updateViewport();
- return true;
-
- }
-
- };
-
- public static double roundToSignificantFigures(double num, int n) {
- if(num == 0) {
- return 0;
- }
-
- final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
- final int power = n - (int) d;
-
- final double magnitude = Math.pow(10, power);
- final long shifted = Math.round(num*magnitude);
- return shifted/magnitude;
- }
-
- protected void drawAxis2(Canvas canvas, RectF viewPort) {
- Rect bounds = new Rect();
- DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
- float[] points;
-
- final int canvasWidth = canvas.getWidth();
- final int canvasHeight = canvas.getHeight();
-
- Paint axisPaint = new Paint();
- axisPaint.setColor(mAxisColor);
- axisPaint.setStrokeWidth(2);
-
- Matrix matrix = getViewportToScreenMatrix(new RectF(0,0,canvasWidth, canvasHeight), viewPort);
-
- if(mDrawXAxis){
- //draw X axis
- points = new float[]{
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mPlotMargins.left, metrics), canvasHeight - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mPlotMargins.bottom, metrics),
- canvasWidth, canvasHeight - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mPlotMargins.bottom, metrics)
- };
- canvas.drawLines(points, axisPaint);
-
- float xPoint = (float) (mXAxisDevision * Math.floor(viewPort.left / mXAxisDevision));
- while(xPoint < viewPort.right+mXAxisDevision/2){
- points[0] = xPoint;
- points[1] = 0;
- points[2] = xPoint;
- points[3] = 0;
- matrix.mapPoints(points);
- points[1] = canvasHeight - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mPlotMargins.bottom, metrics);
- points[3] = canvasHeight - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mPlotMargins.bottom + 10, metrics);
- canvas.drawLines(points, axisPaint);
-
- String label = String.valueOf(xPoint);
- mAxisLabelPaint.getTextBounds(label, 0, label.length(), bounds);
-
- canvas.drawText(label,
- points[0]-bounds.width()/2,
- points[1] + bounds.height() + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, metrics),
- mAxisLabelPaint);
-
- xPoint += mXAxisDevision;
-
- }
-
-
- }
-
- if(mDrawYAxis){
- //draw Y axis
- points = new float[]{
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mPlotMargins.left, metrics), 0,
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mPlotMargins.left, metrics), canvasHeight
- };
- canvas.drawLines(points, axisPaint);
-
- float yPoint = (float) (mYAxisDevision * Math.floor(viewPort.top / mYAxisDevision));
- while(yPoint < viewPort.bottom+mYAxisDevision/2){
- points[0] = 0;
- points[1] = yPoint;
- points[2] = 0;
- points[3] = yPoint;
- matrix.mapPoints(points);
- points[0] = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mPlotMargins.left, metrics);
- points[2] = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mPlotMargins.left + 10, metrics);
- canvas.drawLines(points, axisPaint);
-
- String label = String.valueOf(yPoint);
- mAxisLabelPaint.getTextBounds(label, 0, label.length(), bounds);
- canvas.drawText(label,
- points[0]-bounds.width()-TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, metrics),
- points[1]+bounds.height()/2,
- mAxisLabelPaint);
-
- yPoint += mYAxisDevision;
- }
-
- }
-
- }
-
- protected void drawAxis(Canvas canvas, RectF viewPort) {
-
- Rect bounds = new Rect();
- DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
- float[] points;
-
- final int canvasWidth = canvas.getWidth();
- final int canvasHeight = canvas.getHeight();
-
- Matrix matrix = getViewportToScreenMatrix(new RectF(0,0,canvasWidth, canvasHeight), viewPort);
-
- Paint axisPaint = new Paint();
- axisPaint.setColor(mAxisColor);
- axisPaint.setStrokeWidth(2);
-
- if(mDrawXAxis){
- //draw X axis
- points = new float[]{
- viewPort.left, 0,
- viewPort.right, 0
- };
- matrix.mapPoints(points);
- canvas.drawLines(points, axisPaint);
-
- float xPoint = (float) (mXAxisDevision * Math.floor(viewPort.left / mXAxisDevision));
- while(xPoint < viewPort.right+mXAxisDevision/2){
- if(xPoint != 0.0f){
- points[0] = xPoint;
- points[1] = 0;
- points[2] = xPoint;
- points[3] = 0;
- matrix.mapPoints(points);
- points[1] -= TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, metrics);
- points[3] += TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, metrics);
- canvas.drawLines(points, axisPaint);
-
- String label = String.valueOf(xPoint);
- mAxisLabelPaint.getTextBounds(label, 0, label.length(), bounds);
-
- canvas.drawText(label,
- points[0]-bounds.width()/2,
- points[1] + bounds.height() + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, metrics),
- mAxisLabelPaint);
-
- }
- xPoint += mXAxisDevision;
-
- }
-
-
- }
-
-
- if(mDrawYAxis){
- //draw Y axis
- points = new float[]{
- 0, viewPort.top,
- 0, viewPort.bottom
- };
- matrix.mapPoints(points);
- canvas.drawLines(points, axisPaint);
-
- float yPoint = (float) (mYAxisDevision * Math.floor(viewPort.top / mYAxisDevision));
- while(yPoint < viewPort.bottom+mYAxisDevision/2){
- if(yPoint != 0.0f){
- points[0] = 0;
- points[1] = yPoint;
- points[2] = 0;
- points[3] = yPoint;
- matrix.mapPoints(points);
- points[0] -= TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, metrics);
- points[2] += TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, metrics);
- canvas.drawLines(points, axisPaint);
-
- String label = String.valueOf(yPoint);
- mAxisLabelPaint.getTextBounds(label, 0, label.length(), bounds);
- float textWidth = mAxisLabelPaint.measureText(label);
- canvas.drawText(label,
- points[0]-textWidth-TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, metrics),
- points[1]+bounds.height()/2,
- mAxisLabelPaint);
- }
- yPoint += mYAxisDevision;
- }
-
- }
-
-
-
-
- }
-
- public void autoScaleDomainAndRange() {
-
- /*
- mViewPort.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
- Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
- for(Series series : mSeries){
- Iterator it = series.createIterator();
- while(it.hasNext()){
- float[] point = it.next();
- mViewPort.left = Math.min(mViewPort.left, point[0]);
- mViewPort.right = Math.max(mViewPort.right, point[0]);
- mViewPort.top = Math.min(mViewPort.top, point[1]);
- mViewPort.bottom = Math.max(mViewPort.bottom, point[1]);
- }
- }
- drawFrame(mViewPort);
- */
-
-
- BackgroundTask.runBackgroundTask(new BackgroundTask() {
-
- RectF viewport = new RectF();
-
- @Override
- public void onBackground() {
- viewport.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
- Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
- for(Series series : mSeries){
- Iterator it = series.createIterator();
- while(it.hasNext()){
- float[] point = it.next();
- viewport.left = Math.min(viewport.left, point[0]);
- viewport.right = Math.max(viewport.right, point[0]);
- viewport.top = Math.min(viewport.top, point[1]);
- viewport.bottom = Math.max(viewport.bottom, point[1]);
- }
- }
-
- RectF screen = new RectF(mPlotMargins.left, mPlotMargins.top, getMeasuredWidth(),getMeasuredHeight()-mPlotMargins.height());
- Matrix matrix = new Matrix();
- getViewportToScreenMatrix(screen, viewport).invert(matrix);
- matrix.mapRect(viewport, new RectF(0,0,getMeasuredWidth(), getMeasuredHeight()));
-
-
- }
-
- @Override
- public void onAfter() {
- drawFrame(viewport);
- }
-
- }, mDrawThread);
-
- }
-
- public void zoomInCenter() {
- float scale = 1.3f;
- mTransformMatrix.postScale(scale, scale, getMeasuredWidth()/2, getMeasuredHeight()/2);
- invalidate();
- updateViewport();
- }
-
- public void zoomOutCenter() {
- float scale = 0.7f;
- mTransformMatrix.postScale(scale, scale, getMeasuredWidth()/2, getMeasuredHeight()/2);
- invalidate();
- updateViewport();
- }
-
- private ZoomButtonsController.OnZoomListener mZoomButtonListener = new ZoomButtonsController.OnZoomListener(){
-
- @Override
- public void onVisibilityChanged(boolean visible) {}
-
- @Override
- public void onZoom(boolean zoomIn) {
- if(zoomIn) {
- zoomInCenter();
- } else {
- zoomOutCenter();
- }
-
- }
-
- };
-
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import com.devsmart.android.BackgroundTask;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
-}
+public final class GraphView extends View {
+ public static void drawBitmap(Canvas c, int width, int height, List data, RectF viewport, CoordinateSystem coordinateSystem) {
+ CoordinateSystem mCoordCopy = coordinateSystem.copy();
+ mCoordCopy.interpolate(viewport, new RectF(0, 0, width, height));
+
+ try {
+ c.save();
+ c.scale(1, -1);
+ c.translate(0, -c.getHeight());
+
+ for (DataRenderer r : data) {
+ r.draw(c, viewport, mCoordCopy);
+ }
+ } finally {
+ c.restore();
+ }
+ }
+
+ private static final String KEY_VIEWPORT = "viewport";
+ private static final String KEY_SUPERINSTANCE = "superinstance";
+ private static final long mAnimationTime = 1000;
+
+ private final GestureDetector.SimpleOnGestureListener mSimpleGestureListener = new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ mTransformMatrix.postScale(1.3f, 1.3f, e.getX(), e.getY());
+ invalidate();
+ updateViewport();
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ mTransformMatrix.postTranslate(-distanceX, -distanceY);
+ invalidate();
+ updateViewport();
+ return true;
+ }
+ };
+
+ private final XYScaleGestureDetector.SimpleOnScaleGestureListener mSimpleScaleGestureListener = new XYScaleGestureDetector.SimpleOnScaleGestureListener() {
+ @Override
+ public boolean onScale(XYScaleGestureDetector detector) {
+ mTransformMatrix.postScale(detector.getXScaleFactor(), detector.getYScaleFactor(), detector.getFocusX(), detector.getFocusY());
+ invalidate();
+ updateViewport();
+ return true;
+ }
+ };
+
+ private final ExecutorService mDrawThread = Executors.newSingleThreadExecutor();
+ private final LinkedList mPlotData = new LinkedList();
+ private final Matrix mTransformMatrix = new Matrix();
+ private final Paint mDrawPaint = new Paint();
+ private final Paint mAxisLabelPaint = new Paint();
+ private final Rect mPlotMargins = new Rect();
+
+ private CoordinateSystem mCoordinateSystem = CoordinateSystem.createLinearSystem();
+ private RectF mViewPort = new RectF();
+ private Bitmap mFrontBuffer;
+ private BackgroundDrawTask mBackgroundDrawTask;
+ private GestureDetector mPanGestureDetector;
+ private XYScaleGestureDetector mScaleGestureDetector;
+ private AxisRenderer mAxisRenderer;
+ private Rect mGraphArea;
+ private RectF mViewPortBounds;
+ private Interpolator mAnimationInterpolator = null;
+ private RectF mAnimationDest;
+ private long mAnimationEndTime = 0;
+
+ public GraphView(Context context) {
+ super(context);
+ mAxisRenderer = new SimpleAxisRenderer(getContext());
+ init();
+ }
+
+ public GraphView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.GraphView, 0, 0);
+
+ mAxisRenderer = new SimpleAxisRenderer(getContext());
+
+ mAxisRenderer.setAxisColor(a.getInteger(R.styleable.GraphView_axisColor, Color.BLACK));
+ mAxisRenderer.setLabelColor(a.getInteger(R.styleable.GraphView_axisColor, Color.DKGRAY));
+
+ a.recycle();
+
+ init();
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Bundle retval = new Bundle();
+ retval.putParcelable(KEY_SUPERINSTANCE, super.onSaveInstanceState());
+
+ float[] viewportvalues = new float[4];
+ viewportvalues[0] = mViewPort.left;
+ viewportvalues[1] = mViewPort.top;
+ viewportvalues[2] = mViewPort.right;
+ viewportvalues[3] = mViewPort.bottom;
+ retval.putFloatArray(KEY_VIEWPORT, viewportvalues);
+
+ return retval;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ Bundle bundle = (Bundle) state;
+ super.onRestoreInstanceState(bundle.getParcelable(KEY_SUPERINSTANCE));
+
+ float[] viewportvalues = bundle.getFloatArray(KEY_VIEWPORT);
+ mViewPort.left = viewportvalues[0];
+ mViewPort.top = viewportvalues[1];
+ mViewPort.right = viewportvalues[2];
+ mViewPort.bottom = viewportvalues[3];
+ drawFrame(mViewPort);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ mGraphArea = mAxisRenderer.measureGraphArea(w, h);
+ mCoordinateSystem.interpolate(mViewPort, new RectF(0, 0, mGraphArea.width(), mGraphArea.height()));
+
+ drawFrame(mViewPort);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ cancelAnimation();
+
+ final int action = MotionEventCompat.getActionMasked(event);
+ switch (action) {
+ case MotionEvent.ACTION_UP:
+ doBoundsCheck();
+ break;
+ }
+
+ boolean retval = mPanGestureDetector.onTouchEvent(event);
+ retval |= mScaleGestureDetector.onTouchEvent(event);
+
+ return retval;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ doAnimation();
+ if (mFrontBuffer != null) {
+ canvas.save();
+ canvas.translate(mGraphArea.left, mGraphArea.top);
+ canvas.drawBitmap(mFrontBuffer, mTransformMatrix, mDrawPaint);
+ canvas.restore();
+ }
+
+ mAxisRenderer.drawAxis(canvas, getMeasuredWidth(), getMeasuredHeight(), getDisplayViewPort(), getCoordinateSystem());
+ }
+
+ public AxisRenderer getAxisRenderer() {
+ return mAxisRenderer;
+ }
+
+ public void addSeries(DataRenderer series) {
+ mPlotData.add(series);
+ drawFrame(mViewPort);
+ }
+
+ public void removeSeries(DataRenderer series) {
+ mPlotData.remove(series);
+ drawFrame(mViewPort);
+ }
+
+ public void setViewportBounds(RectF bounds) {
+ mViewPortBounds = bounds;
+ }
+
+ public void doBoundsCheck() {
+ if (mViewPortBounds != null) {
+ RectF newViewport = getDisplayViewPort();
+ if (newViewport.width() > mViewPortBounds.width()) {
+ newViewport.left = mViewPortBounds.left;
+ newViewport.right = mViewPortBounds.right;
+ }
+ if (newViewport.height() > mViewPortBounds.height()) {
+ newViewport.top = mViewPortBounds.top;
+ newViewport.bottom = mViewPortBounds.bottom;
+ }
+ if (newViewport.left < mViewPortBounds.left) {
+ newViewport.offset(mViewPortBounds.left - newViewport.left, 0);
+ }
+ if (newViewport.right > mViewPortBounds.right) {
+ newViewport.offset(mViewPortBounds.right - newViewport.right, 0);
+ }
+ if (newViewport.bottom > mViewPortBounds.bottom) {
+ newViewport.offset(0, mViewPortBounds.bottom - newViewport.bottom);
+ }
+ if (newViewport.top < mViewPortBounds.top) {
+ newViewport.offset(0, mViewPortBounds.top - newViewport.top);
+ }
+
+ mAnimationInterpolator = new DecelerateInterpolator();
+ mAnimationEndTime = System.currentTimeMillis() + mAnimationTime;
+ mAnimationDest = newViewport;
+ invalidate();
+ }
+ }
+
+ public RectF getDisplayViewPort() {
+ RectF rect = new RectF(0, 0, mGraphArea.width(), mGraphArea.height());
+
+ Matrix m = new Matrix();
+ mTransformMatrix.invert(m);
+ m.postScale(1, -1);
+ m.postTranslate(0, mGraphArea.height());
+ m.mapRect(rect);
+
+ mCoordinateSystem.getInverse().mapRect(rect);
+
+ return rect;
+ }
+
+ public CoordinateSystem getCoordinateSystem() {
+ CoordinateSystem retval = mCoordinateSystem.copy();
+ retval.interpolate(getDisplayViewPort(), new RectF(0, 0, mGraphArea.width(), mGraphArea.height()));
+
+ return retval;
+ }
+
+ public void setDisplayViewPort(RectF viewport) {
+ mTransformMatrix.reset();
+ mViewPort = new RectF(viewport);
+
+ if (mGraphArea != null) {
+ mCoordinateSystem.interpolate(mViewPort, new RectF(0, 0, mGraphArea.width(), mGraphArea.height()));
+ drawFrame(viewport);
+ }
+ }
+
+ public void updateViewport() {
+ RectF newViewport = getDisplayViewPort();
+ drawFrame(newViewport);
+ }
+
+ private void init() {
+ mPanGestureDetector = new GestureDetector(mSimpleGestureListener);
+ mScaleGestureDetector = new XYScaleGestureDetector(getContext(), mSimpleScaleGestureListener);
+ mDrawPaint.setFilterBitmap(true);
+ mViewPort.set(0, 0, 1, 1);
+ mTransformMatrix.reset();
+
+ //defaults
+ mPlotMargins.set(20, 0, 0, 20);
+ mAxisLabelPaint.setColor(Color.DKGRAY);
+ mAxisLabelPaint.setTextSize(15.0f);
+ mAxisLabelPaint.setAntiAlias(true);
+ }
+
+ private void doAnimation() {
+ if (mAnimationInterpolator != null) {
+ long now = System.currentTimeMillis();
+ if (mAnimationEndTime <= now) {
+ mAnimationInterpolator = null;
+ drawFrame(mAnimationDest);
+ return;
+ }
+
+ float done = 1 - ((float) (mAnimationEndTime - now) / mAnimationTime);
+ done = mAnimationInterpolator.getInterpolation(done);
+ RectF newViewport = new RectF();
+ RectF currentViewport = getDisplayViewPort();
+ newViewport.left = (1 - done) * currentViewport.left + done * mAnimationDest.left;
+ newViewport.right = (1 - done) * currentViewport.right + done * mAnimationDest.right;
+ newViewport.top = (1 - done) * currentViewport.top + done * mAnimationDest.top;
+ newViewport.bottom = (1 - done) * currentViewport.bottom + done * mAnimationDest.bottom;
+ drawFrame(newViewport);
+ }
+ }
+
+ private void cancelAnimation() {
+ mAnimationInterpolator = null;
+ }
+
+ private void drawFrame(final RectF viewport) {
+ if (mBackgroundDrawTask != null) {
+ mBackgroundDrawTask.mCanceled = true;
+ }
+
+ mBackgroundDrawTask = new BackgroundDrawTask(viewport);
+ BackgroundTask.runBackgroundTask(mBackgroundDrawTask, mDrawThread);
+ }
+
+ private final class BackgroundDrawTask extends BackgroundTask {
+ private int width;
+ private int height;
+ private Bitmap mDrawBuffer;
+ private boolean mCanceled = false;
+ private final RectF viewport;
+ private ArrayList mData;
+ private CoordinateSystem mCoordCopy;
+
+ public BackgroundDrawTask(RectF view) {
+ this.viewport = new RectF(view);
+ if (mCoordinateSystem == null || mGraphArea == null) {
+ mCanceled = true;
+ return;
+ }
+
+ this.width = mGraphArea.width();
+ this.height = mGraphArea.height();
+ this.mCoordCopy = mCoordinateSystem.copy();
+ this.mCoordCopy.interpolate(viewport, new RectF(0, 0, width, height));
+
+ this.mData = new ArrayList(mPlotData);
+ }
+
+ @Override
+ public void onBackground() {
+ if (!mCanceled) {
+ mDrawBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
+ Canvas c = new Canvas(mDrawBuffer);
+
+ try {
+ c.save();
+ c.scale(1, -1);
+ c.translate(0, -c.getHeight());
+
+ for (DataRenderer r : mData) {
+ r.draw(c, viewport, mCoordCopy);
+ }
+ } finally {
+ c.restore();
+ }
+ }
+ }
+
+ @Override
+ public void onAfter() {
+ if (!mCanceled) {
+ mFrontBuffer = mDrawBuffer;
+ mViewPort = viewport;
+ mTransformMatrix.reset();
+ mCoordinateSystem = mCoordCopy;
+ invalidate();
+ } else if (mDrawBuffer != null) {
+ mDrawBuffer.recycle();
+ }
+ mDrawBuffer = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/devsmart/plotter/LineGraphDataRenderer.java b/src/com/devsmart/plotter/LineGraphDataRenderer.java
new file mode 100644
index 0000000..132b23a
--- /dev/null
+++ b/src/com/devsmart/plotter/LineGraphDataRenderer.java
@@ -0,0 +1,92 @@
+package com.devsmart.plotter;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+
+import com.devsmart.PeekableIterator;
+
+public class LineGraphDataRenderer implements DataRenderer {
+
+ protected final Paint mPointPaint = new Paint();
+ protected Series mSeries;
+
+ public LineGraphDataRenderer(Series series, int color) {
+ mSeries = series;
+ mPointPaint.setColor(color);
+ mPointPaint.setStrokeWidth(2.0f);
+ }
+
+ RectF pixel = new RectF();
+ RectF pixelBin = new RectF();
+
+ public void draw(Canvas canvas, RectF viewPort, CoordinateSystem coordSystem) {
+ final float xBinWidth = viewPort.width() / canvas.getWidth();
+ pixelBin.left = viewPort.left - xBinWidth;
+ pixelBin.right = viewPort.left;
+ pixelBin.bottom = Float.POSITIVE_INFINITY;
+ pixelBin.top = Float.NEGATIVE_INFINITY;
+
+ float[] lastpoint = new float[]{Float.NaN, Float.NaN};
+
+ PeekableIterator it = new PeekableIterator(mSeries.createIterator());
+
+
+ //findPixelBinLessThan(pixelBin, it);
+ while (it.hasNext()) {
+ float[] point = it.next();
+ lastpoint[0] = point[0];
+ lastpoint[1] = point[1];
+ if (it.peek()[0] > viewPort.left) {
+ break;
+ }
+ }
+
+ coordSystem.mapPoints(lastpoint);
+
+ boolean findOneMore = false;
+ while (it.hasNext()) {
+ pixelBin.offset(xBinWidth, 0);
+ pixelBin.bottom = Float.POSITIVE_INFINITY;
+ pixelBin.top = Float.NEGATIVE_INFINITY;
+
+ if (fillPixelBin(pixelBin, it)) {
+ //draw pixel
+ coordSystem.mapRect(pixel, pixelBin);
+ canvas.drawLine(lastpoint[0], lastpoint[1], pixel.left, pixel.top, mPointPaint);
+ lastpoint[0] = pixel.left;
+ lastpoint[1] = pixel.top;
+ if (findOneMore) {
+ break;
+ }
+ }
+ if (it.peek() != null && it.peek()[0] > viewPort.right) {
+ findOneMore = true;
+ }
+ }
+ }
+
+ @Override
+ public void setPaintColor(int color) {
+ mPointPaint.setColor(color);
+ }
+
+ private boolean fillPixelBin(RectF pixelBin, PeekableIterator it) {
+ boolean retval = false;
+ float[] point;
+ while (it.hasNext()) {
+ point = it.peek();
+ if (point[0] > pixelBin.right) {
+ break;
+ }
+
+ if (point[0] >= pixelBin.left) {
+ pixelBin.bottom = Math.min(pixelBin.bottom, point[1]);
+ pixelBin.top = Math.max(pixelBin.top, point[1]);
+ retval = true;
+ }
+ it.next();
+ }
+ return retval;
+ }
+}
\ No newline at end of file
diff --git a/src/com/devsmart/plotter/LineGraphView.java b/src/com/devsmart/plotter/LineGraphView.java
deleted file mode 100644
index c0b8181..0000000
--- a/src/com/devsmart/plotter/LineGraphView.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package com.devsmart.plotter;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-
-import com.devsmart.PeekableIterator;
-
-public class LineGraphView extends GraphView {
-
- protected Paint mPointPaint = new Paint();
-
- public LineGraphView(Context context) {
- super(context);
- init();
- }
-
- public LineGraphView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
-
- protected void init() {
- mPointPaint.setColor(Color.GREEN);
- mPointPaint.setStrokeWidth(2.0f);
- }
-
- protected void drawGraph(Canvas canvas, RectF viewPort){
-
- //clear the canvas
- canvas.drawColor(mBackgroundColor);
-
- Matrix matrix = getViewportToScreenMatrix(new RectF(0,0,canvas.getWidth(), canvas.getHeight()), viewPort);
-
- RectF pixel = new RectF();
-
- RectF pixelBin = new RectF();
- final float xBinWidth = viewPort.width()/canvas.getWidth();
- pixelBin.left = viewPort.left-xBinWidth;
- pixelBin.right = viewPort.left;
- pixelBin.bottom = Float.POSITIVE_INFINITY;
- pixelBin.top = Float.NEGATIVE_INFINITY;
-
- float[] lastpoint = new float[]{Float.NaN, Float.NaN};
- for(Series series : mSeries){
- PeekableIterator it = new PeekableIterator(series.createIterator());
-
-
- //findPixelBinLessThan(pixelBin, it);
- while(it.hasNext()){
- float[] point = it.next();
- lastpoint[0] = point[0];
- lastpoint[1] = point[1];
- if(it.peek()[0] > viewPort.left){
- break;
- }
- }
- matrix.mapPoints(lastpoint);
-
- boolean findOneMore = false;
- while(it.hasNext()){
- pixelBin.offset(xBinWidth, 0);
- pixelBin.bottom = Float.POSITIVE_INFINITY;
- pixelBin.top = Float.NEGATIVE_INFINITY;
-
- if(fillPixelBin(pixelBin, it)){
- //draw pixel
- matrix.mapRect(pixel, pixelBin);
- canvas.drawLine(lastpoint[0], lastpoint[1], pixel.left, pixel.top, mPointPaint);
- lastpoint[0] = pixel.left;
- lastpoint[1] = pixel.top;
- if(findOneMore) {
- break;
- }
- }
- if(it.peek()[0] > viewPort.right){
- findOneMore = true;
- }
-
- }
- }
-
- }
-
- private boolean findPixelBinLessThan(RectF pixelBin, PeekableIterator it) {
- boolean retval = false;
- float[] point;
-
- while(it.hasNext()){
-
- point = it.next();
- pixelBin.bottom = Math.min(pixelBin.bottom, point[1]);
- pixelBin.top = Math.max(pixelBin.top, point[1]);
-
- point = it.peek();
-
- if(point[0] >= pixelBin.right){
- break;
- }
-
- if(point[0] >= pixelBin.left && point[0] < pixelBin.right){
- pixelBin.bottom = Float.POSITIVE_INFINITY;
- pixelBin.top = Float.NEGATIVE_INFINITY;
- }
-
-
- retval = true;
- }
-
- return retval;
- }
-
- private boolean fillPixelBin(RectF pixelBin, PeekableIterator it) {
- boolean retval = false;
- float[] point;
- while(it.hasNext()){
- point = it.peek();
- if(point[0] > pixelBin.right) {
- break;
- }
-
- if(point[0] >= pixelBin.left) {
- pixelBin.bottom = Math.min(pixelBin.bottom, point[1]);
- pixelBin.top = Math.max(pixelBin.top, point[1]);
- retval = true;
- }
- it.next();
- }
- return retval;
- }
-
-
-}
diff --git a/src/com/devsmart/plotter/LineRenderer.java b/src/com/devsmart/plotter/LineRenderer.java
new file mode 100644
index 0000000..3942c11
--- /dev/null
+++ b/src/com/devsmart/plotter/LineRenderer.java
@@ -0,0 +1,51 @@
+package com.devsmart.plotter;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+
+import java.util.List;
+
+/**
+ * Created by sgowen on 4/13/15.
+ */
+public final class LineRenderer implements DataRenderer {
+ public static class XYPair {
+ public float x;
+ public float y;
+
+ public XYPair(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+ }
+
+ private final List mLinesFromOriginList;
+ private final Paint mPaint = new Paint();
+
+ public LineRenderer(List linesFromOriginList, int color) {
+ mLinesFromOriginList = linesFromOriginList;
+ mPaint.setColor(color);
+ mPaint.setStrokeWidth(1.0f);
+ mPaint.setAntiAlias(true);
+ mPaint.setStyle(Paint.Style.STROKE);
+ }
+
+ @Override
+ public void draw(Canvas canvas, RectF viewPort, CoordinateSystem coordSystem) {
+ float[] point = new float[2];
+ float[] origin = {0, 0};
+ coordSystem.mapPoints(origin);
+ for (XYPair xyPair : mLinesFromOriginList) {
+ point[0] = xyPair.x;
+ point[1] = xyPair.y;
+ coordSystem.mapPoints(point);
+ canvas.drawLine(point[0], origin[1], point[0], point[1], mPaint);
+ }
+ }
+
+ @Override
+ public void setPaintColor(int color) {
+ mPaint.setColor(color);
+ }
+}
\ No newline at end of file
diff --git a/src/com/devsmart/plotter/LinearFunction.java b/src/com/devsmart/plotter/LinearFunction.java
new file mode 100644
index 0000000..bc3e6ae
--- /dev/null
+++ b/src/com/devsmart/plotter/LinearFunction.java
@@ -0,0 +1,42 @@
+package com.devsmart.plotter;
+
+public class LinearFunction implements AxisFunction {
+
+ private float a = 1;
+ private float m = 0;
+
+ public LinearFunction(){
+
+ }
+
+ public LinearFunction(float a, float m) {
+ this.a = a;
+ this.m = m;
+ }
+
+ @Override
+ public float value(float x) {
+ return a * x + m;
+ }
+
+ @Override
+ public AxisFunction inverse() {
+ return new LinearFunction(1/a, -m/a);
+ }
+
+ @Override
+ public void interpolate(float[] x, float[] y) {
+ float top = y[1]-y[0];
+ float bottom = x[1]-x[0];
+
+ this.a = top / bottom;
+ this.m = -a*x[0];
+
+ }
+
+ @Override
+ public AxisFunction copy() {
+ return new LinearFunction(a, m);
+ }
+
+}
diff --git a/src/com/devsmart/plotter/OversampleFunctionRenderer.java b/src/com/devsmart/plotter/OversampleFunctionRenderer.java
new file mode 100644
index 0000000..16bdc57
--- /dev/null
+++ b/src/com/devsmart/plotter/OversampleFunctionRenderer.java
@@ -0,0 +1,163 @@
+package com.devsmart.plotter;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+
+import java.util.Arrays;
+
+public final class OversampleFunctionRenderer implements DataRenderer {
+ private final FunctionRenderer2.GraphFunction mFunction;
+ private final Paint mPaint = new Paint();
+ private final MinMax mMinMax = new MinMax();
+ private final float[] mPoints = new float[4];
+ private double[] mSamplePoints;
+ private double mSampleRate;
+
+ public OversampleFunctionRenderer(FunctionRenderer2.GraphFunction f, double[] samplePoints, int color) {
+ mFunction = f;
+ mSamplePoints = new double[samplePoints.length];
+ System.arraycopy(samplePoints, 0, mSamplePoints, 0, mSamplePoints.length);
+ Arrays.sort(mSamplePoints);
+
+ initPaint(color);
+ }
+
+ public OversampleFunctionRenderer(FunctionRenderer2.GraphFunction f, double sampleRate, int color) {
+ mFunction = f;
+ mSampleRate = sampleRate;
+
+ initPaint(color);
+ }
+
+ @Override
+ public void draw(Canvas canvas, RectF viewPort, CoordinateSystem coordSystem) {
+ if (mSamplePoints != null) {
+ drawUsingSamplePoints(canvas, viewPort, coordSystem);
+ } else {
+ drawUsingSamplerate(canvas, viewPort, coordSystem);
+ }
+ }
+
+ @Override
+ public void setPaintColor(int color) {
+ mPaint.setColor(color);
+ }
+
+ private void initPaint(int color) {
+ mPaint.setColor(color);
+ mPaint.setStrokeWidth(2.0f);
+ mPaint.setAntiAlias(true);
+ mPaint.setStyle(Paint.Style.STROKE);
+ }
+
+ private void drawUsingSamplerate(Canvas canvas, RectF viewPort, CoordinateSystem coordSystem) {
+ float[] points = new float[2];
+ final double pixelWidth = viewPort.width() / (double) canvas.getWidth();
+
+ mMinMax.clear();
+ double lastX = viewPort.left - pixelWidth;
+
+ Path p = new Path();
+
+ points[0] = viewPort.left;
+ points[1] = (float) mFunction.value(viewPort.left);
+ coordSystem.mapPoints(points);
+ p.moveTo(points[0], points[1]);
+
+ for (double x = viewPort.left; x <= viewPort.right; x += mSampleRate) {
+ final double y = mFunction.value(x);
+ mMinMax.addValue(y);
+
+ if (x >= lastX + pixelWidth) {
+ drawLine(p, (float) x, coordSystem);
+ lastX = x;
+ mMinMax.clear();
+ }
+ }
+
+ canvas.drawPath(p, mPaint);
+ }
+
+ private void drawLine(Path path, float x, CoordinateSystem coordSystem) {
+ mPoints[0] = x;
+ mPoints[1] = (float) mMinMax.min;
+ mPoints[2] = x;
+ mPoints[3] = (float) mMinMax.max;
+
+ coordSystem.mapPoints(mPoints);
+ if (FunctionRenderer2.isRealNumber(mPoints[0]) && FunctionRenderer2.isRealNumber(mPoints[1])) {
+ path.lineTo(mPoints[0], mPoints[1]);
+ }
+ if (FunctionRenderer2.isRealNumber(mPoints[2]) && FunctionRenderer2.isRealNumber(mPoints[3])) {
+ path.lineTo(mPoints[2], mPoints[3]);
+ }
+
+ path.moveTo(mPoints[0], mPoints[1]);
+ }
+
+ private void drawUsingSamplePoints(Canvas canvas, RectF viewPort, CoordinateSystem coordSystem) {
+ float[] points = new float[2];
+ final double pixelWidth = viewPort.width() / (double) canvas.getWidth();
+
+ int start = Arrays.binarySearch(mSamplePoints, viewPort.left);
+ if (start < 0) {
+ start = -start - 2;
+ }
+ if (start < 0) {
+ start = 0;
+ }
+
+ int end = Arrays.binarySearch(mSamplePoints, viewPort.right);
+ if (end < 0) {
+ end = -end;
+ }
+ if (end >= mSamplePoints.length) {
+ end = mSamplePoints.length;
+ }
+
+ Path p = new Path();
+ mMinMax.clear();
+
+ double lastX = mSamplePoints[start];
+ points[0] = (float) lastX;
+ points[1] = (float) mFunction.value(lastX);
+ coordSystem.mapPoints(points);
+ p.moveTo(points[0], points[1]);
+
+ for (int i = start + 1; i < end; i++) {
+ double x = mSamplePoints[i];
+ final double y = mFunction.value(x);
+ mMinMax.addValue(y);
+
+ if (x >= lastX + pixelWidth) {
+ drawLine(p, (float) x, coordSystem);
+ lastX = x;
+ mMinMax.clear();
+ }
+ }
+
+ canvas.drawPath(p, mPaint);
+ }
+
+ private static class MinMax {
+ double min = Double.NaN;
+ double max = Double.NaN;
+
+ public void addValue(double value) {
+ if (value < min || Double.isNaN(min)) {
+ min = value;
+ }
+
+ if (value > max || Double.isNaN(max)) {
+ max = value;
+ }
+ }
+
+ public void clear() {
+ min = Double.NaN;
+ max = Double.NaN;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/devsmart/plotter/PointRenderer.java b/src/com/devsmart/plotter/PointRenderer.java
new file mode 100644
index 0000000..1244008
--- /dev/null
+++ b/src/com/devsmart/plotter/PointRenderer.java
@@ -0,0 +1,39 @@
+package com.devsmart.plotter;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+
+public class PointRenderer implements DataRenderer {
+
+ private float[] mPoints;
+ private final Paint mPaint = new Paint();
+
+ public PointRenderer(float[] points, int color) {
+ mPoints = points;
+ mPaint.setColor(color);
+ mPaint.setStrokeWidth(5.0f);
+ mPaint.setAntiAlias(true);
+ mPaint.setStyle(Paint.Style.STROKE);
+ }
+
+ @Override
+ public void draw(Canvas canvas, RectF viewPort, CoordinateSystem coordSystem) {
+
+ float[] point = new float[2];
+ for (int i = 0; i < mPoints.length; i = i + 2) {
+ if (viewPort.contains(mPoints[i], mPoints[i + 1])) {
+ point[0] = mPoints[i];
+ point[1] = mPoints[i + 1];
+
+ coordSystem.mapPoints(point);
+ canvas.drawCircle(point[0], point[1], 2.0f, mPaint);
+ }
+ }
+ }
+
+ @Override
+ public void setPaintColor(int color) {
+ mPaint.setColor(color);
+ }
+}
\ No newline at end of file
diff --git a/src/com/devsmart/plotter/Series.java b/src/com/devsmart/plotter/Series.java
index b0dabcd..9d18f1c 100644
--- a/src/com/devsmart/plotter/Series.java
+++ b/src/com/devsmart/plotter/Series.java
@@ -2,10 +2,6 @@
import java.util.Iterator;
-import android.graphics.RectF;
-
-
public interface Series {
-
- public Iterator createIterator();
-}
+ Iterator createIterator();
+}
\ No newline at end of file
diff --git a/src/com/devsmart/plotter/SimpleAxisRenderer.java b/src/com/devsmart/plotter/SimpleAxisRenderer.java
index 6202b0b..a8e5cea 100644
--- a/src/com/devsmart/plotter/SimpleAxisRenderer.java
+++ b/src/com/devsmart/plotter/SimpleAxisRenderer.java
@@ -1,177 +1,193 @@
package com.devsmart.plotter;
+import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.util.TypedValue;
-import com.devsmart.MathUtils;
-
-public class SimpleAxisRenderer implements AxisRenderer {
-
- int numDivisions = 5;
- boolean mDrawXAxis = true;
- boolean mDrawYAxis = true;
- int mAxisColor = Color.BLACK;
- Rect mPlotMargins = new Rect(20, 0, 0, 20);
- Paint mAxisLabelPaint = new Paint();
- Paint mAxisTickPaint = new Paint();
- String mXAxisLabel = "Wavelength";
- String mYAxisLabel = "Intensity";
-
- public SimpleAxisRenderer() {
-
- }
-
- protected boolean init = false;
- private float[] mYAxis;
- private float[] mXAxis;
- float[] points = new float[4];
- Rect bounds = new Rect();
-
- protected void init(GraphView graphview) {
- DisplayMetrics metrics = graphview.getContext().getResources().getDisplayMetrics();
-
- final int canvasWidth = graphview.getWidth();
- final int canvasHeight = graphview.getHeight();
-
- mAxisLabelPaint.setColor(Color.BLACK);
- mAxisLabelPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 15, metrics));
- mAxisLabelPaint.setAntiAlias(true);
-
- mAxisTickPaint.setColor(Color.DKGRAY);
- mAxisTickPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, metrics));
- mAxisTickPaint.setAntiAlias(true);
-
-
- mAxisLabelPaint.getTextBounds("1", 0, 1, bounds);
- float axisLabelHeight = bounds.height();
-
- mAxisTickPaint.getTextBounds("1", 0, 1, bounds);
- float tickLabelHeight = bounds.height();
-
- float axisLabelBoundery = axisLabelHeight + tickLabelHeight + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, metrics);
-
-
- float height = canvasHeight - axisLabelBoundery - mPlotMargins.height();
-
- mYAxis = new float[]{axisLabelBoundery + mPlotMargins.left, mPlotMargins.top,
- axisLabelBoundery + mPlotMargins.left, mPlotMargins.top + height};
-
- mXAxis = new float[]{axisLabelBoundery + mPlotMargins.left, canvasHeight - axisLabelBoundery - mPlotMargins.bottom,
- canvasWidth - mPlotMargins.right, canvasHeight - axisLabelBoundery - mPlotMargins.bottom};
-
- init = true;
- }
-
-
- @Override
- public void drawAxis(Canvas canvas, RectF viewPort, GraphView graphview) {
-
- if(!init){
- init(graphview);
- }
-
- DisplayMetrics metrics = graphview.getContext().getResources().getDisplayMetrics();
-
- final int canvasWidth = graphview.getWidth();
- final int canvasHeight = graphview.getHeight();
-
- Paint axisPaint = new Paint();
- axisPaint.setColor(mAxisColor);
- axisPaint.setStrokeWidth(2);
-
- Matrix matrix = graphview.getViewportToScreenMatrix(new RectF(0,0,canvasWidth, canvasHeight), viewPort);
-
-
- if(mDrawXAxis) {
-
- //draw axis
- canvas.drawLines(mXAxis, axisPaint);
-
- //draw label
- mAxisLabelPaint.getTextBounds(mXAxisLabel, 0, mXAxisLabel.length(), bounds);
- float y = canvasHeight - mPlotMargins.bottom + bounds.bottom + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, metrics);
- canvas.drawText(mXAxisLabel, (mXAxis[2]-mXAxis[0])/2 - bounds.width()/2 + mXAxis[0], y, mAxisLabelPaint);
-
- //draw ticks
- final float dist = viewPort.width() / numDivisions;
- float xPoint = (float) (dist * Math.floor(viewPort.left / dist));
- while(xPoint < viewPort.right+dist){
- points[0] = xPoint;
- points[1] = 0;
- points[2] = xPoint;
- points[3] = 0;
- matrix.mapPoints(points);
- points[1] = mXAxis[1];
- points[3] = mXAxis[1] - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, metrics);
- canvas.drawLines(points, axisPaint);
-
- String label = getTickLabel(xPoint);
- mAxisTickPaint.getTextBounds(label, 0, label.length(), bounds);
-
- canvas.drawText(label,
- points[0]-bounds.width()/2,
- points[1] + bounds.height() + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, metrics),
- mAxisTickPaint);
-
- xPoint += dist;
-
- }
- }
-
- if(mDrawYAxis){
- //draw Y axis
- canvas.drawLines(mYAxis, axisPaint);
-
- //draw label
- mAxisLabelPaint.getTextBounds(mYAxisLabel, 0, mYAxisLabel.length(), bounds);
- canvas.save();
- points[0] = mPlotMargins.left;
- points[1] = (mYAxis[3] - mYAxis[1])/2 + bounds.width()/2;
- canvas.rotate(-90, points[0], points[1]);
- canvas.drawText(mYAxisLabel, points[0], points[1], mAxisLabelPaint);
- canvas.restore();
-
- final float dist = viewPort.height() / numDivisions;
- float yPoint = (float) (dist * Math.floor(viewPort.top / dist));
- while(yPoint < viewPort.bottom+dist){
- points[0] = 0;
- points[1] = yPoint;
- points[2] = 0;
- points[3] = yPoint;
- matrix.mapPoints(points);
- points[0] = mYAxis[0];
- points[2] = mYAxis[0] + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, metrics);
- canvas.drawLines(points, axisPaint);
-
- String label = getTickLabel(yPoint);
- mAxisTickPaint.getTextBounds(label, 0, label.length(), bounds);
- canvas.save();
- points[2] = points[0]-bounds.height()/2-TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, metrics);
- points[3] = points[1]+bounds.width()/2;
- canvas.rotate(-90, points[2], points[3]);
- canvas.drawText(label,
- points[2], points[3],
- mAxisTickPaint);
- canvas.restore();
-
- yPoint += dist;
- }
-
- }
-
- }
-
- protected String getTickLabel(float value) {
- return String.valueOf(MathUtils.round(value, 1));
- //return String.valueOf(value);
- }
-
-
-
-}
+public final class SimpleAxisRenderer implements AxisRenderer {
+ private int numDivisions = 5;
+ private boolean mDrawXAxis = true;
+ private boolean mDrawYAxis = true;
+ private Rect mPlotMargins = new Rect(20, 0, 0, 20);
+ private final Paint mAxisLabelPaint = new Paint();
+ private final Paint mAxisTickPaint = new Paint();
+ private String mXAxisLabel = "Wavelength";
+ private String mYAxisLabel = "Intensity";
+ private final DisplayMetrics mDisplayMetrics;
+ private float[] mYAxis;
+ private float[] mXAxis;
+ private float[] points = new float[4];
+ private Rect bounds = new Rect();
+ private Rect graphArea = new Rect();
+ private Matrix m = new Matrix();
+
+ public SimpleAxisRenderer(Context context) {
+ mDisplayMetrics = context.getResources().getDisplayMetrics();
+
+ init();
+ }
+
+ public SimpleAxisRenderer(GraphView graphview) {
+ mDisplayMetrics = graphview.getContext().getResources().getDisplayMetrics();
+
+ init();
+ }
+
+ @Override
+ public void setAxisColor(int color) {
+ mAxisTickPaint.setColor(color);
+ }
+
+ @Override
+ public void setLabelColor(int color) {
+ mAxisLabelPaint.setColor(color);
+ }
+
+ @Override
+ public void setYAxisLabel(String label) {
+ mYAxisLabel = label;
+ }
+
+ @Override
+ public void setXAxisLabel(String label) {
+ mXAxisLabel = label;
+ }
+
+ @Override
+ public void drawAxis(Canvas canvas, final int canvasWidth, final int canvasHeight, RectF viewPort, CoordinateSystem coordSystem) {
+ measureGraphArea(canvasWidth, canvasHeight);
+
+ m.setRectToRect(new RectF(0, 0, graphArea.width(), graphArea.height()), new RectF(graphArea), ScaleToFit.FILL);
+ m.postScale(1, -1);
+ m.postTranslate(0, graphArea.height());
+
+ //Debug axis display
+ //canvas.drawText(viewPort.toString(), 50, 50, mAxisTickPaint);
+
+ if (mDrawXAxis) {
+ //draw axis
+ canvas.drawLines(mXAxis, mAxisTickPaint);
+
+ //draw label
+ mAxisLabelPaint.getTextBounds(mXAxisLabel, 0, mXAxisLabel.length(), bounds);
+ float y = canvasHeight - mPlotMargins.bottom + bounds.bottom + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, mDisplayMetrics);
+ canvas.drawText(mXAxisLabel, (mXAxis[2] - mXAxis[0]) / 2 - bounds.width() / 2 + mXAxis[0], y, mAxisLabelPaint);
+
+ //draw ticks
+ final float dist = viewPort.width() / numDivisions;
+ float xPoint = (float) (dist * Math.floor(viewPort.left / dist));
+ while (xPoint < viewPort.right + dist) {
+ points[0] = xPoint;
+ points[1] = 0;
+ points[2] = xPoint;
+ points[3] = 0;
+ coordSystem.mapPoints(points);
+ m.mapPoints(points);
+ points[1] = mXAxis[1];
+ points[3] = mXAxis[1] - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, mDisplayMetrics);
+
+ if (points[0] >= mXAxis[0]) {
+ canvas.drawLines(points, mAxisTickPaint);
+
+ String label = getTickLabel(xPoint);
+ mAxisTickPaint.getTextBounds(label, 0, label.length(), bounds);
+
+ canvas.drawText(label, points[0] - bounds.width() / 2, points[1] + bounds.height() + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, mDisplayMetrics), mAxisTickPaint);
+ }
+
+ xPoint += dist;
+ }
+ }
+
+ if (mDrawYAxis) {
+ //draw Y axis
+ canvas.drawLines(mYAxis, mAxisTickPaint);
+
+ //draw label
+ mAxisLabelPaint.getTextBounds(mYAxisLabel, 0, mYAxisLabel.length(), bounds);
+ canvas.save();
+ points[0] = mPlotMargins.left;
+ points[1] = (mYAxis[3] - mYAxis[1]) / 2 + bounds.width() / 2;
+ canvas.rotate(-90, points[0], points[1]);
+ canvas.drawText(mYAxisLabel, points[0], points[1], mAxisLabelPaint);
+ canvas.restore();
+
+ final float dist = viewPort.height() / numDivisions;
+ float yPoint = (float) (dist * Math.floor(viewPort.top / dist));
+ while (yPoint < viewPort.bottom + dist) {
+ points[0] = 0;
+ points[1] = yPoint;
+ points[2] = 0;
+ points[3] = yPoint;
+ coordSystem.mapPoints(points);
+ m.mapPoints(points);
+ points[0] = mYAxis[0];
+ points[2] = mYAxis[0] + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, mDisplayMetrics);
+
+ if (points[1] <= mYAxis[3]) {
+ canvas.drawLines(points, mAxisTickPaint);
+
+ String label = getTickLabel(yPoint);
+ mAxisTickPaint.getTextBounds(label, 0, label.length(), bounds);
+ canvas.save();
+ points[2] = points[0] - bounds.height() / 2 - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, mDisplayMetrics);
+ points[3] = points[1] + bounds.width() / 2;
+ canvas.rotate(-90, points[2], points[3]);
+ canvas.drawText(label, points[2], points[3], mAxisTickPaint);
+ canvas.restore();
+ }
+
+ yPoint += dist;
+ }
+ }
+ }
+
+ @Override
+ public Rect measureGraphArea(int screenWidth, int screenHeight) {
+ calcBounds(screenWidth, screenHeight);
+
+ graphArea.left = (int) Math.floor(mXAxis[0]);
+ graphArea.right = (int) Math.ceil(mXAxis[2]);
+ graphArea.top = (int) Math.floor(mYAxis[1]);
+ graphArea.bottom = (int) Math.ceil(mYAxis[3]);
+
+ return graphArea;
+ }
+
+ private void init() {
+ mAxisLabelPaint.setColor(Color.BLACK);
+ mAxisLabelPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 15, mDisplayMetrics));
+ mAxisLabelPaint.setAntiAlias(true);
+
+ mAxisTickPaint.setColor(Color.DKGRAY);
+ mAxisTickPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, mDisplayMetrics));
+ mAxisTickPaint.setAntiAlias(true);
+ }
+
+ private void calcBounds(final int canvasWidth, final int canvasHeight) {
+ mAxisLabelPaint.getTextBounds("1", 0, 1, bounds);
+ float axisLabelHeight = bounds.height();
+
+ mAxisTickPaint.getTextBounds("1", 0, 1, bounds);
+ float tickLabelHeight = bounds.height();
+
+ float axisLabelBoundery = axisLabelHeight + tickLabelHeight + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, mDisplayMetrics);
+
+ float height = canvasHeight - axisLabelBoundery - mPlotMargins.height();
+
+ mYAxis = new float[]{axisLabelBoundery + mPlotMargins.left, mPlotMargins.top, axisLabelBoundery + mPlotMargins.left, mPlotMargins.top + height};
+
+ mXAxis = new float[]{axisLabelBoundery + mPlotMargins.left, canvasHeight - axisLabelBoundery - mPlotMargins.bottom, canvasWidth - mPlotMargins.right, canvasHeight - axisLabelBoundery - mPlotMargins.bottom};
+ }
+
+ private String getTickLabel(float value) {
+ return String.format("%g", value);
+ }
+}
\ No newline at end of file
diff --git a/src/com/devsmart/plotter/SimpleSeries.java b/src/com/devsmart/plotter/SimpleSeries.java
index b618a11..46c5d5f 100644
--- a/src/com/devsmart/plotter/SimpleSeries.java
+++ b/src/com/devsmart/plotter/SimpleSeries.java
@@ -3,17 +3,12 @@
import java.util.ArrayList;
import java.util.Iterator;
-import android.graphics.RectF;
-
public class SimpleSeries implements Series {
-
- public ArrayList mData = new ArrayList();
-
- @Override
- public Iterator createIterator() {
- return mData.iterator();
- }
-
+ public ArrayList mData = new ArrayList();
-}
+ @Override
+ public Iterator createIterator() {
+ return mData.iterator();
+ }
+}
\ No newline at end of file
diff --git a/src/com/devsmart/plotter/XYScaleGestureDetector.java b/src/com/devsmart/plotter/XYScaleGestureDetector.java
index 3dcccbf..00c9093 100644
--- a/src/com/devsmart/plotter/XYScaleGestureDetector.java
+++ b/src/com/devsmart/plotter/XYScaleGestureDetector.java
@@ -225,6 +225,9 @@ public boolean onTouchEvent(MotionEvent event) {
break;
case MotionEvent.ACTION_MOVE:
+ if(event.getPointerCount() < 2){
+ return false;
+ }
if (mSloppyGesture) {
// Initiate sloppy gestures if we've moved outside of the slop area.
final float edgeSlop = mEdgeSlop;