package com.github.mikephil.charting.listener;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.data.BarLineScatterCandleData;
import com.github.mikephil.charting.data.BarLineScatterCandleRadarDataSet;
import com.github.mikephil.charting.data.DrawingContext;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.utils.Highlight;
import com.github.mikephil.charting.utils.PointD;
/**
* TouchListener for Bar-, Line-, Scatter- and CandleStickChart with handles all
* touch interaction. Longpress == Zoom out. Double-Tap == Zoom in.
*
* @author Philipp Jahoda
*/
public class BarLineChartTouchListener<T extends BarLineChartBase<? extends BarLineScatterCandleData<? extends BarLineScatterCandleRadarDataSet<? extends Entry>>>> extends SimpleOnGestureListener implements OnTouchListener {
private Matrix mMatrix = new Matrix();
private Matrix mSavedMatrix = new Matrix();
/** point where the touch action started */
private PointF mTouchStartPoint = new PointF();
/** center between two pointers (fingers on the display) */
private PointF mTouchPointCenter = new PointF();
// states
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int DRAWING = 2;
private static final int MOVE_POINT = 3;
private static final int X_ZOOM = 4;
private static final int Y_ZOOM = 5;
private static final int PINCH_ZOOM = 6;
/** if true, user can draw on the chart */
private boolean mDrawingEnabled = false;
private int mTouchMode = NONE;
private float mSavedXDist = 1f;
private float mSavedYDist = 1f;
private float mSavedDist = 1f;
private long mStartTimestamp = 0;
private Highlight mLastHighlighted;
private T mChart;
private DrawingContext mDrawingContext;
private GestureDetector mGestureDetector;
public BarLineChartTouchListener(T chart, Matrix start) {
this.mChart = chart;
this.mMatrix = start;
mGestureDetector = new GestureDetector(chart.getContext(), this);
mDrawingContext = new DrawingContext();
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mTouchMode == NONE) {
mGestureDetector.onTouchEvent(event);
}
if (!mChart.isDragScaleEnabled() && !mDrawingEnabled)
return true;
mDrawingContext.init(mChart.getDrawListener(), mChart.isAutoFinishEnabled());
BarLineScatterCandleData<? extends BarLineScatterCandleRadarDataSet<? extends Entry>> data = mChart.getDataCurrent();
// Handle touch events here...
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
if (event.getPointerCount() == 1 && mDrawingEnabled) {
if (mLastHighlighted != null) {
Entry highlightedEntry = mChart.getDataCurrent().getEntryForHighlight(
mLastHighlighted);
Entry currentHoveredEntry = mChart.getEntryByTouchPoint(event.getX(),
event.getY());
if (highlightedEntry != null && highlightedEntry == currentHoveredEntry) {
mTouchMode = MOVE_POINT;
mDrawingContext.setMovingEntry(currentHoveredEntry);
break;
}
}
mTouchMode = DRAWING;
// TODO not always create new drawing sets
mStartTimestamp = System.currentTimeMillis();
mDrawingContext.createNewDrawingDataSet(data);
Log.i("Drawing", "New drawing data set created");
} else {
mSavedMatrix.set(mMatrix);
}
mTouchStartPoint.set(event.getX(), event.getY());
break;
case MotionEvent.ACTION_POINTER_DOWN:
if (event.getPointerCount() == 2) {
long deltaT = System.currentTimeMillis() - mStartTimestamp;
if ((mTouchMode == DRAWING && deltaT < 1000) || !mDrawingEnabled) {
mDrawingContext.deleteLastDrawingEntry(data);
// get the distance between the pointers on the x-axis
mSavedXDist = getXDist(event);
// get the distance between the pointers on the y-axis
mSavedYDist = getYDist(event);
// get the total distance between the pointers
mSavedDist = spacing(event);
if (mSavedDist > 10f) {
if (mChart.isPinchZoomEnabled()) {
mTouchMode = PINCH_ZOOM;
} else {
if (mSavedXDist > mSavedYDist)
mTouchMode = X_ZOOM;
else
mTouchMode = Y_ZOOM;
}
mSavedMatrix.set(mMatrix);
midPoint(mTouchPointCenter, event);
mChart.disableScroll();
}
}
}
break;
case MotionEvent.ACTION_UP:
if (mTouchMode == DRAWING) {
long deltaT = System.currentTimeMillis() - mStartTimestamp;
if (deltaT < 1000 && Math.abs(event.getX() - mTouchStartPoint.x) < 25f) {
mDrawingContext.deleteLastDrawingEntry(data);
onSingleTapConfirmed(event);
Log.i("Drawing", "Drawing aborted");
} else {
mDrawingContext.finishNewDrawingEntry(data);
mChart.notifyDataSetChanged();
Log.i("Drawing", "Drawing finished");
}
} else {
mChart.enableScroll();
}
mDrawingContext.setMovingEntry(null);
mTouchMode = NONE;
break;
case MotionEvent.ACTION_MOVE:
if (mTouchMode == DRAWING || mTouchMode == MOVE_POINT) {
PointD p = mChart.getValuesByTouchPoint(event.getX(), event.getY());
int xIndex = (int) p.x;
float yVal = (float) p.y;
if (xIndex < 0)
xIndex = 0;
if (xIndex >= data.getXValCount()) {
xIndex = data.getXValCount() - 1;
}
boolean added = false;
if (mTouchMode == MOVE_POINT) {
mDrawingContext.getMovingEntry().setVal(yVal);
// do not allow x index to change for the moment
// mDrawingContext.getMovingEntry().setXIndex(xIndex);
mDrawingContext.notifyEntryMoved(data);
added = true;
} else {
Entry entry = new Entry((float) yVal, xIndex);
added = mDrawingContext.addNewDrawingEntry(entry, data);
}
if (added) {
mChart.notifyDataSetChanged();
}
} else if (((mTouchMode == NONE && !mDrawingEnabled) || (mTouchMode != DRAG && event
.getPointerCount() == 3))
&& Math.abs(distance(event.getX(), mTouchStartPoint.x, event.getY(),
mTouchStartPoint.y)) > 25f) {
// has to be in no mode, when drawing not enabled, or 3
// fingers
mSavedMatrix.set(mMatrix);
mTouchStartPoint.set(event.getX(), event.getY());
mTouchMode = DRAG;
mChart.disableScroll();
} else if (mTouchMode == DRAG) {
mMatrix.set(mSavedMatrix);
PointF dragPoint = new PointF(event.getX(), event.getY());
// check if axis is inverted
if (!mChart.isInvertYAxisEnabled()) {
mMatrix.postTranslate(dragPoint.x - mTouchStartPoint.x, dragPoint.y
- mTouchStartPoint.y);
} else {
mMatrix.postTranslate(dragPoint.x - mTouchStartPoint.x, -(dragPoint.y
- mTouchStartPoint.y));
}
} else if (mTouchMode == X_ZOOM || mTouchMode == Y_ZOOM || mTouchMode == PINCH_ZOOM) {
// get the distance between the pointers of the touch
// event
float totalDist = spacing(event);
if (totalDist > 10f) {
// get the translation
PointF t = getTrans(mTouchPointCenter.x, mTouchPointCenter.y);
// take actions depending on the activated touch
// mode
if (mTouchMode == PINCH_ZOOM) {
float scale = totalDist / mSavedDist; // total
// scale
mMatrix.set(mSavedMatrix);
mMatrix.postScale(scale, scale, t.x, t.y);
} else if (mTouchMode == X_ZOOM) {
float xDist = getXDist(event);
float scaleX = xDist / mSavedXDist; // x-axis
// scale
mMatrix.set(mSavedMatrix);
mMatrix.postScale(scaleX, 1f, t.x, t.y);
} else if (mTouchMode == Y_ZOOM) {
float yDist = getYDist(event);
float scaleY = yDist / mSavedYDist; // y-axis
// scale
mMatrix.set(mSavedMatrix);
// y-axis comes from top to bottom, revert y
mMatrix.postScale(1f, scaleY, t.x, t.y);
}
}
}
break;
}
// Perform the transformation
mMatrix = mChart.refreshTouch(mMatrix);
return true; // indicate event was handled
}
/**
* returns the touch mode the listener is currently in
*
* @return
*/
public int getTouchMode() {
return mTouchMode;
}
/**
* enables drawing by finger on the listener
*
* @param mDrawingEnabled
*/
public void setDrawingEnabled(boolean mDrawingEnabled) {
this.mDrawingEnabled = mDrawingEnabled;
}
/**
* returns the distance between two points
*
* @param eventX
* @param startX
* @param eventY
* @param startY
* @return
*/
private static float distance(float eventX, float startX, float eventY, float startY) {
float dx = eventX - startX;
float dy = eventY - startY;
return (float) Math.sqrt(dx * dx + dy * dy);
}
/**
* returns the center point between two pointer touch points
*
* @param point
* @param event
*/
private static void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2f, y / 2f);
}
/**
* returns the distance between two pointer touch points
*
* @param event
* @return
*/
private static float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
/**
* calculates the distance on the x-axis between two pointers (fingers on
* the display)
*
* @param e
* @return
*/
private static float getXDist(MotionEvent e) {
float x = Math.abs(e.getX(0) - e.getX(1));
return x;
}
/**
* calculates the distance on the y-axis between two pointers (fingers on
* the display)
*
* @param e
* @return
*/
private static float getYDist(MotionEvent e) {
float y = Math.abs(e.getY(0) - e.getY(1));
return y;
}
/**
* returns the correct translation depending on the provided x and y touch
* points
*
* @param e
* @return
*/
public PointF getTrans(float x, float y) {
float xTrans = x - mChart.getOffsetLeft();
float yTrans = 0f;
// check if axis is inverted
if (!mChart.isInvertYAxisEnabled()) {
yTrans = -(mChart.getMeasuredHeight() - y - mChart.getOffsetBottom());
} else {
yTrans = -(y - mChart.getOffsetTop());
}
return new PointF(xTrans, yTrans);
}
/**
* returns the matrix object the listener holds
*
* @return
*/
public Matrix getMatrix() {
return mMatrix;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
// check if double-tap zooming is enabled
if (mChart.isDoubleTapToZoomEnabled()) {
PointF trans = getTrans(e.getX(), e.getY());
mChart.zoomIn(trans.x, trans.y);
Log.i("BarlineChartTouch", "Double-Tap, Zooming In, x: " + trans.x + ", y: " + trans.y);
}
return super.onDoubleTap(e);
}
@Override
public void onLongPress(MotionEvent e) {
mChart.fitScreen();
Log.i("BarlineChartTouch",
"Longpress, resetting zoom and drag, adjusting chart bounds to screen.");
// PointF trans = getTrans(e.getX(), e.getY());
//
// mChart.zoomOut(trans.x, trans.y);
//
// Log.i("BarlineChartTouch", "Longpress, Zooming Out, x: " + trans.x +
// ", y: " + trans.y);
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Highlight h = mChart.getHighlightByTouchPoint(e.getX(), e.getY());
// check if the same was highlighted again
if (h == null || h.equalTo(mLastHighlighted)) {
mChart.highlightTouch(null);
mLastHighlighted = null;
} else {
mLastHighlighted = h;
mChart.highlightTouch(h);
}
return true;
}
}