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;