How to Pan, Zoom and Scale


How to Pan, Zoom and Scale

[doc_header] [last_updated_for version=0.5.1]

Overview

In this example you'll learn how to pan, zoom and scale your data in an XYPlot.

Walkthrough

layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent"
	android:orientation="vertical"
	android:layout_height="fill_parent">
	<com.androidplot.xy.XYPlot
		android:id="@+id/mySimpleXYPlot"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:layout_marginTop="5px"
		android:layout_marginLeft="0px"
		android:layout_marginRight="0px"
		title="The economy" /></LinearLayout>

plotActivity.java

/***********************************
 * @author David Buezas (david.buezas at gmail.com)
 * Feel free to copy, modify and use the source as it fits you.
 */
package davidbuezas.multitouchExample;

import java.text.DecimalFormat;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.PointF;
import android.os.Bundle;
import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import com.androidplot.xy.BoundaryMode;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.LineAndPointRenderer;
import com.androidplot.xy.XYPlot;

import davidbuezas.multitouch.R;
public class plotActivity extends Activity implements OnTouchListener {
	private XYPlot mySimpleXYPlot;
	private SimpleXYSeries mySeries;
	private PointF minXY;
	private PointF maxXY;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main); 
		mySimpleXYPlot = (XYPlot) findViewById(R.id.mySimpleXYPlot);
		mySimpleXYPlot.setOnTouchListener(this);

		//Plot layout configurations
		mySimpleXYPlot.getGraphWidget().setTicksPerRangeLabel(1);
		mySimpleXYPlot.getGraphWidget().setTicksPerDomainLabel(1);
		mySimpleXYPlot.getGraphWidget().setRangeValueFormat(
				new DecimalFormat("#####.##"));
		mySimpleXYPlot.getGraphWidget().setDomainValueFormat(
				new DecimalFormat("#####.##"));
		mySimpleXYPlot.getGraphWidget().setRangeLabelWidth(25);
		mySimpleXYPlot.setRangeLabel("");
		mySimpleXYPlot.setDomainLabel("");
		mySimpleXYPlot.disableAllMarkup();

		//Creation of the series
		Vector<Double> vector=new Vector<Double>();
		for (double x=0.0;x<Math.PI*5;x+=Math.PI/20){
			vector.add(x);
			vector.add(Math.sin(x));
		}
		mySeries = new SimpleXYSeries(vector);

		mySimpleXYPlot.addSeries(mySeries, LineAndPointRenderer.class,
				new LineAndPointFormatter(Color.rgb(0, 200, 0), Color.rgb(200,
						0, 0)));

		mySimpleXYPlot.redraw();

		//Set of internal variables for keeping track of the boundaries
		mySimpleXYPlot.calculateMinMaxVals();
		minXY=new PointF(mySimpleXYPlot.getCalculatedMinX().floatValue(),mySimpleXYPlot.getCalculatedMinY().floatValue());
		maxXY=new PointF(mySimpleXYPlot.getCalculatedMaxX().floatValue(),mySimpleXYPlot.getCalculatedMaxY().floatValue());

	}

	// Definition of the touch states
	static final int NONE = 0;
	static final int ONE_FINGER_DRAG = 1;
	static final int TWO_FINGERS_DRAG = 2;
	int mode = NONE;

	PointF firstFinger;
	float lastScrolling;
	float distBetweenFingers;
	float lastZooming;

	@Override
	public boolean onTouch(View arg0, MotionEvent event) {
		switch (event.getAction() & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN: // Start gesture
			firstFinger = new PointF(event.getX(), event.getY());
			mode = ONE_FINGER_DRAG;
			break;
		case MotionEvent.ACTION_UP: 
		case MotionEvent.ACTION_POINTER_UP:
			//When the gesture ends, a thread is created to give inertia to the scrolling and zoom 
			Timer t = new Timer();
				t.schedule(new TimerTask() {
					@Override
					public void run() {
						while(Math.abs(lastScrolling)>1f || Math.abs(lastZooming-1)<1.01){ 
						lastScrolling*=.8;
						scroll(lastScrolling);
						lastZooming+=(1-lastZooming)*.2;
						zoom(lastZooming);
						mySimpleXYPlot.setDomainBoundaries(minXY.x, maxXY.x, BoundaryMode.AUTO);
						try {
							mySimpleXYPlot.postRedraw();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						// the thread lives until the scrolling and zooming are imperceptible
					}
					}
				}, 0);

		case MotionEvent.ACTION_POINTER_DOWN: // second finger
			distBetweenFingers = spacing(event);
			// the distance check is done to avoid false alarms
			if (distBetweenFingers > 5f) {
				mode = TWO_FINGERS_DRAG;
			}
			break;
		case MotionEvent.ACTION_MOVE:
			if (mode == ONE_FINGER_DRAG) {
				PointF oldFirstFinger=firstFinger;
				firstFinger=new PointF(event.getX(), event.getY());
				lastScrolling=oldFirstFinger.x-firstFinger.x;
				scroll(lastScrolling);
				lastZooming=(firstFinger.y-oldFirstFinger.y)/mySimpleXYPlot.getHeight();
				if (lastZooming<0)
					lastZooming=1/(1-lastZooming);
				else
					lastZooming+=1;
				zoom(lastZooming);
				mySimpleXYPlot.setDomainBoundaries(minXY.x, maxXY.x, BoundaryMode.AUTO);
				mySimpleXYPlot.redraw();

			} else if (mode == TWO_FINGERS_DRAG) {
				float oldDist =distBetweenFingers; 
				distBetweenFingers=spacing(event);
				lastZooming=oldDist/distBetweenFingers;
				zoom(lastZooming);
				mySimpleXYPlot.setDomainBoundaries(minXY.x, maxXY.x, BoundaryMode.AUTO);
				mySimpleXYPlot.redraw();
			}
			break;
		}
		return true;
	}

	private void zoom(float scale) {
		float domainSpan = maxXY.x	- minXY.x;
		float domainMidPoint = maxXY.x		- domainSpan / 2.0f;
		float offset = domainSpan * scale / 2.0f;
		minXY.x=domainMidPoint- offset;
		maxXY.x=domainMidPoint+offset;
	}

	private void scroll(float pan) {
		float domainSpan = maxXY.x	- minXY.x;
		float step = domainSpan / mySimpleXYPlot.getWidth();
		float offset = pan * step;
		minXY.x+= offset;
		maxXY.x+= offset;
	}

	private float spacing(MotionEvent event) {
		float x = event.getX(0) - event.getX(1);
		float y = event.getY(0) - event.getY(1);
		return FloatMath.sqrt(x * x + y * y);
	}
}