AndroidPlot multitouch zoom & scroll

I’m playing around AndroidPlot library. I’ve needed to add zomm and scroll functionality. There is one example at AndroidPlot wiki, but a bit outdated, and I don’t like the idea of messing around view events in the activiti. So I’ve created my own sample:

main.xml layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <pl.flex_it.androidplot.MultitouchPlot
    	android:id="@+id/multitouchPlot"
    	android:layout_width="fill_parent"
    	android:layout_height="fill_parent"
    	android:layout_marginTop="10px"
    	android:layout_marginLeft="10px"
    	android:layout_marginRight="10px"
    	title="A Simple multitouch XYPlot Example"/>

</LinearLayout>

MultitouchAndroidplotActivity.java file:

package pl.flex_it.androidplot;

import java.util.Arrays;

import com.androidplot.series.XYSeries;
import com.androidplot.xy.BoundaryMode;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.SimpleXYSeries;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;

/**
*
* This example is based on:
* - multitouch example, by David Buezas (david.buezas at gmail.com) and Michael
* from http://androidplot.com/wiki/A_Simple_XYPlot_with_multi-touch_zooming_and_scrolling
* - AndroidPlot quickstart example http://androidplot.com/wiki/Quickstart
* No license was given with this samples, but I assume that use it for free on BSD-like license ;-)
*
* @author Marcin Lepicki (marcin.lepicki at flex-it.pl)
*
*/
public class MultitouchAndroidplotActivity extends Activity
{

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

MultitouchPlot multitouchPlot = (MultitouchPlot) findViewById(R.id.multitouchPlot);

// Create two arrays of y-values to plot:
Number[] series1Numbers = {1, 8, 5, 2, 7, 4};
Number[] series2Numbers = {4, 6, 3, 8, 2, 10};

// Turn the above arrays into XYSeries:
XYSeries series1 = new SimpleXYSeries(
Arrays.asList(series1Numbers), // SimpleXYSeries takes a List so turn our array into a List
SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, // Y_VALS_ONLY means use the element index as the x value
"Obwód brzucha"); // Set the display title of the series

// Same as above, for series2
XYSeries series2 = new SimpleXYSeries(Arrays.asList(series2Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY,
"Series2");

// Create a formatter to use for drawing a series using LineAndPointRenderer:
LineAndPointFormatter series1Format = new LineAndPointFormatter(
Color.rgb(0, 200, 0), // line color
Color.rgb(0, 100, 0), // point color
Color.rgb(150, 190, 150)); // fill color (optional)

// Add series1 to the xyplot:
multitouchPlot.addSeries(series1, series1Format);

// Same as above, with series2:
multitouchPlot.addSeries(series2, new LineAndPointFormatter(Color.rgb(0, 0, 200), Color.rgb(0, 0, 100),
Color.rgb(150, 150, 190)));

// Reduce the number of range labels
multitouchPlot.setTicksPerRangeLabel(3);

// By default, AndroidPlot displays developer guides to aid in laying out your plot.
// To get rid of them call disableAllMarkup():
multitouchPlot.disableAllMarkup();

multitouchPlot.setRangeBoundaries(0, 10, BoundaryMode.FIXED);
multitouchPlot.setDomainBoundaries(0, 2.2, BoundaryMode.FIXED);

}
}

MultitouchPlot.java file:

package pl.flex_it.androidplot;

import android.content.Context;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import com.androidplot.series.XYSeries;
import com.androidplot.xy.BoundaryMode;
import com.androidplot.xy.XYPlot;
import com.androidplot.xy.XYSeriesFormatter;

/**
*
* This example is based on:
* - multitouch example, by David Buezas (david.buezas at gmail.com) and Michael
* from http://androidplot.com/wiki/A_Simple_XYPlot_with_multi-touch_zooming_and_scrolling
* - AndroidPlot quickstart example http://androidplot.com/wiki/Quickstart
* No license was given with this samples, but I assume that use it for free on BSD-like license ;-)
*
* @author Marcin Lepicki (marcin.lepicki at flex-it.pl)
*
*/
public class MultitouchPlot extends XYPlot implements OnTouchListener
{

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

private Number minXSeriesValue;
private Number maxXSeriesValue;
private Number minYSeriesValue;
private Number maxYSeriesValue;

private PointF firstFinger;
private float lastScrolling;
private float distBetweenFingers;

private Number newMinX;
private Number newMaxX;

public MultitouchPlot(Context context, String title)
{
super(context, title);
initTouchHandling();
}

public MultitouchPlot(Context context, AttributeSet attributes)
{
super(context, attributes);
initTouchHandling();
}

public MultitouchPlot(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
initTouchHandling();
}

private void initTouchHandling()
{
this.setOnTouchListener(this);
}

public boolean addSeries(XYSeries series, XYSeriesFormatter formatter)
{
//Overriden to compute min and max series values
for(int i = 0; i &lt; series.size(); i++) { if(minXSeriesValue == null || minXSeriesValue.doubleValue() &gt; series.getX(i).doubleValue())
minXSeriesValue = series.getX(i);
if(maxXSeriesValue == null || maxXSeriesValue.doubleValue() &lt; series.getX(i).doubleValue()) maxXSeriesValue = series.getX(i); if(minYSeriesValue == null || minYSeriesValue.doubleValue() &gt; series.getY(i).doubleValue())
minYSeriesValue = series.getY(i);
if(maxYSeriesValue == null || maxYSeriesValue.doubleValue() &lt; series.getX(i).doubleValue()) maxYSeriesValue = series.getY(i); } return super.addSeries(series, formatter); } public boolean onTouch(View view, MotionEvent motionEvent) { switch(motionEvent.getAction() &amp; MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: //start gesture firstFinger = new PointF(motionEvent.getX(), motionEvent.getY()); mode = ONE_FINGER_DRAG; break; case MotionEvent.ACTION_POINTER_DOWN: //second finger { distBetweenFingers = distance(motionEvent); // the distance check is done to avoid false alarms if (distBetweenFingers &gt; 5f || distBetweenFingers &lt; -5f) mode = TWO_FINGERS_DRAG; break; } case MotionEvent.ACTION_POINTER_UP: //end zoom //should I count pointers and change mode after only one is left? mode = ONE_FINGER_DRAG; break; case MotionEvent.ACTION_MOVE: if(mode == ONE_FINGER_DRAG) { calculateMinMaxVals(); final PointF oldFirstFinger = firstFinger; firstFinger = new PointF(motionEvent.getX(), motionEvent.getY()); lastScrolling = oldFirstFinger.x - firstFinger.x; scroll(lastScrolling); fixBoundariesForScroll(); setDomainBoundaries(newMinX, newMaxX, BoundaryMode.FIXED); redraw(); } else if(mode == TWO_FINGERS_DRAG) { calculateMinMaxVals(); final float oldDist = distBetweenFingers; final float newDist = distance(motionEvent); if(oldDist &gt; 0 &amp;&amp; newDist &lt; 0 || oldDist &lt; 0 &amp;&amp; newDist &gt; 0) //sign change! Fingers have crossed ;-)
break;

distBetweenFingers = newDist;

zoom(oldDist / distBetweenFingers);

fixBoundariesForZoom();
setDomainBoundaries(newMinX, newMaxX, BoundaryMode.FIXED);
redraw();
}
break;
}

return true;
}

private void scroll(float pan)
{
float calculatedMinX = getCalculatedMinX().floatValue();
float calculatedMaxX = getCalculatedMaxX().floatValue();
final float domainSpan = calculatedMaxX - calculatedMinX;
final float step = domainSpan / getWidth();
final float offset = pan * step;

newMinX = calculatedMinX + offset;
newMaxX = calculatedMaxX + offset;
}

private void fixBoundariesForScroll()
{
float diff = newMaxX.floatValue() - newMinX.floatValue();
if(newMinX.floatValue() &lt; minXSeriesValue.floatValue()) { newMinX = minXSeriesValue; newMaxX = newMinX.floatValue() + diff; } if(newMaxX.floatValue() &gt; maxXSeriesValue.floatValue())
{
newMaxX = maxXSeriesValue;
newMinX = newMaxX.floatValue() - diff;
}
}

private float distance(MotionEvent event)
{
final float x = event.getX(0) - event.getX(1);
return x;
}

private void zoom(float scale)
{
if(Float.isInfinite(scale) || Float.isNaN(scale) || (scale &gt; -0.001 &amp;&amp; scale &lt; 0.001)) //sanity check
return;

float calculatedMinX = getCalculatedMinX().floatValue();
float calculatedMaxX = getCalculatedMaxX().floatValue();
final float domainSpan = calculatedMaxX - calculatedMinX;
final float domainMidPoint = calculatedMaxX - domainSpan / 2.0f;
final float offset = domainSpan * scale / 2.0f;
newMinX = domainMidPoint - offset;
newMaxX = domainMidPoint + offset;
}

private void fixBoundariesForZoom()
{
if(newMinX.floatValue() &lt; minXSeriesValue.floatValue()) { newMinX = minXSeriesValue; } if(newMaxX.floatValue() &gt; maxXSeriesValue.floatValue())
{
newMaxX = maxXSeriesValue;
}
}
}

If you want, you can download complete eclipse project here

 

7 thoughts on “AndroidPlot multitouch zoom & scroll

  1. Yes, sir! ;-) You can use my code freely – just remember to follow AndroidPlot license (I believe it’s “Simplified BSD License”)

  2. Hi!
    You have an error at MultitouchPlot.java addseries at the last if is series.getY(i) in place of series.getX(i).

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>