package com.github.mikephil.charting.utils;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.view.View;
/**
* Class that contains information about the charts current viewport settings, including offsets, scale & translation
* levels, ...
*
* @author Philipp Jahoda
*/
public class ViewPortHandler {
/**
* matrix used for touch events
*/
protected final Matrix mMatrixTouch = new Matrix();
/**
* this rectangle defines the area in which graph values can be drawn
*/
protected RectF mContentRect = new RectF();
protected float mChartWidth = 0f;
protected float mChartHeight = 0f;
/**
* minimum scale value on the y-axis
*/
private float mMinScaleY = 1f;
/**
* maximum scale value on the y-axis
*/
private float mMaxScaleY = Float.MAX_VALUE;
/**
* minimum scale value on the x-axis
*/
private float mMinScaleX = 1f;
/**
* maximum scale value on the x-axis
*/
private float mMaxScaleX = Float.MAX_VALUE;
/**
* contains the current scale factor of the x-axis
*/
private float mScaleX = 1f;
/**
* contains the current scale factor of the y-axis
*/
private float mScaleY = 1f;
/**
* current translation (drag distance) on the x-axis
*/
private float mTransX = 0f;
/**
* current translation (drag distance) on the y-axis
*/
private float mTransY = 0f;
/**
* offset that allows the chart to be dragged over its bounds on the x-axis
*/
private float mTransOffsetX = 0f;
/**
* offset that allows the chart to be dragged over its bounds on the x-axis
*/
private float mTransOffsetY = 0f;
/**
* Constructor - don't forget calling setChartDimens(...)
*/
public ViewPortHandler() {
}
/**
* Sets the width and height of the chart.
*
* @param width
* @param height
*/
public void setChartDimens(float width, float height) {
float offsetLeft = this.offsetLeft();
float offsetTop = this.offsetTop();
float offsetRight = this.offsetRight();
float offsetBottom = this.offsetBottom();
mChartHeight = height;
mChartWidth = width;
restrainViewPort(offsetLeft, offsetTop, offsetRight, offsetBottom);
}
public boolean hasChartDimens() {
if (mChartHeight > 0 && mChartWidth > 0)
return true;
else
return false;
}
public void restrainViewPort(float offsetLeft, float offsetTop, float offsetRight,
float offsetBottom) {
mContentRect.set(offsetLeft, offsetTop, mChartWidth - offsetRight, mChartHeight
- offsetBottom);
}
public float offsetLeft() {
return mContentRect.left;
}
public float offsetRight() {
return mChartWidth - mContentRect.right;
}
public float offsetTop() {
return mContentRect.top;
}
public float offsetBottom() {
return mChartHeight - mContentRect.bottom;
}
public float contentTop() {
return mContentRect.top;
}
public float contentLeft() {
return mContentRect.left;
}
public float contentRight() {
return mContentRect.right;
}
public float contentBottom() {
return mContentRect.bottom;
}
public float contentWidth() {
return mContentRect.width();
}
public float contentHeight() {
return mContentRect.height();
}
public RectF getContentRect() {
return mContentRect;
}
public MPPointF getContentCenter() {
return MPPointF.getInstance(mContentRect.centerX(), mContentRect.centerY());
}
public float getChartHeight() {
return mChartHeight;
}
public float getChartWidth() {
return mChartWidth;
}
/**
* Returns the smallest extension of the content rect (width or height).
*
* @return
*/
public float getSmallestContentExtension() {
return Math.min(mContentRect.width(), mContentRect.height());
}
/**
* ################ ################ ################ ################
*/
/** CODE BELOW THIS RELATED TO SCALING AND GESTURES */
/**
* Zooms in by 1.4f, x and y are the coordinates (in pixels) of the zoom
* center.
*
* @param x
* @param y
*/
public Matrix zoomIn(float x, float y) {
Matrix save = new Matrix();
zoomIn(x, y, save);
return save;
}
public void zoomIn(float x, float y, Matrix outputMatrix) {
outputMatrix.reset();
outputMatrix.set(mMatrixTouch);
outputMatrix.postScale(1.4f, 1.4f, x, y);
}
/**
* Zooms out by 0.7f, x and y are the coordinates (in pixels) of the zoom
* center.
*/
public Matrix zoomOut(float x, float y) {
Matrix save = new Matrix();
zoomOut(x, y, save);
return save;
}
public void zoomOut(float x, float y, Matrix outputMatrix) {
outputMatrix.reset();
outputMatrix.set(mMatrixTouch);
outputMatrix.postScale(0.7f, 0.7f, x, y);
}
/**
* Zooms out to original size.
* @param outputMatrix
*/
public void resetZoom(Matrix outputMatrix) {
outputMatrix.reset();
outputMatrix.set(mMatrixTouch);
outputMatrix.postScale(1.0f, 1.0f, 0.0f, 0.0f);
}
/**
* Post-scales by the specified scale factors.
*
* @param scaleX
* @param scaleY
* @return
*/
public Matrix zoom(float scaleX, float scaleY) {
Matrix save = new Matrix();
zoom(scaleX, scaleY, save);
return save;
}
public void zoom(float scaleX, float scaleY, Matrix outputMatrix) {
outputMatrix.reset();
outputMatrix.set(mMatrixTouch);
outputMatrix.postScale(scaleX, scaleY);
}
/**
* Post-scales by the specified scale factors. x and y is pivot.
*
* @param scaleX
* @param scaleY
* @param x
* @param y
* @return
*/
public Matrix zoom(float scaleX, float scaleY, float x, float y) {
Matrix save = new Matrix();
zoom(scaleX, scaleY, x, y, save);
return save;
}
public void zoom(float scaleX, float scaleY, float x, float y, Matrix outputMatrix) {
outputMatrix.reset();
outputMatrix.set(mMatrixTouch);
outputMatrix.postScale(scaleX, scaleY, x, y);
}
/**
* Sets the scale factor to the specified values.
*
* @param scaleX
* @param scaleY
* @return
*/
public Matrix setZoom(float scaleX, float scaleY) {
Matrix save = new Matrix();
setZoom(scaleX, scaleY, save);
return save;
}
public void setZoom(float scaleX, float scaleY, Matrix outputMatrix) {
outputMatrix.reset();
outputMatrix.set(mMatrixTouch);
outputMatrix.setScale(scaleX, scaleY);
}
/**
* Sets the scale factor to the specified values. x and y is pivot.
*
* @param scaleX
* @param scaleY
* @param x
* @param y
* @return
*/
public Matrix setZoom(float scaleX, float scaleY, float x, float y) {
Matrix save = new Matrix();
save.set(mMatrixTouch);
save.setScale(scaleX, scaleY, x, y);
return save;
}
protected float[] valsBufferForFitScreen = new float[9];
/**
* Resets all zooming and dragging and makes the chart fit exactly it's
* bounds.
*/
public Matrix fitScreen() {
Matrix save = new Matrix();
fitScreen(save);
return save;
}
/**
* Resets all zooming and dragging and makes the chart fit exactly it's
* bounds. Output Matrix is available for those who wish to cache the object.
*/
public void fitScreen(Matrix outputMatrix) {
mMinScaleX = 1f;
mMinScaleY = 1f;
outputMatrix.set(mMatrixTouch);
float[] vals = valsBufferForFitScreen;
for (int i = 0; i < 9; i++) {
vals[i] = 0;
}
outputMatrix.getValues(vals);
// reset all translations and scaling
vals[Matrix.MTRANS_X] = 0f;
vals[Matrix.MTRANS_Y] = 0f;
vals[Matrix.MSCALE_X] = 1f;
vals[Matrix.MSCALE_Y] = 1f;
outputMatrix.setValues(vals);
}
/**
* Post-translates to the specified points. Less Performant.
*
* @param transformedPts
* @return
*/
public Matrix translate(final float[] transformedPts) {
Matrix save = new Matrix();
translate(transformedPts, save);
return save;
}
/**
* Post-translates to the specified points. Output matrix allows for caching objects.
*
* @param transformedPts
* @return
*/
public void translate(final float[] transformedPts, Matrix outputMatrix) {
outputMatrix.reset();
outputMatrix.set(mMatrixTouch);
final float x = transformedPts[0] - offsetLeft();
final float y = transformedPts[1] - offsetTop();
outputMatrix.postTranslate(-x, -y);
}
protected Matrix mCenterViewPortMatrixBuffer = new Matrix();
/**
* Centers the viewport around the specified position (x-index and y-value)
* in the chart. Centering the viewport outside the bounds of the chart is
* not possible. Makes most sense in combination with the
* setScaleMinima(...) method.
*
* @param transformedPts the position to center view viewport to
* @param view
* @return save
*/
public void centerViewPort(final float[] transformedPts, final View view) {
Matrix save = mCenterViewPortMatrixBuffer;
save.reset();
save.set(mMatrixTouch);
final float x = transformedPts[0] - offsetLeft();
final float y = transformedPts[1] - offsetTop();
save.postTranslate(-x, -y);
refresh(save, view, true);
}
/**
* buffer for storing the 9 matrix values of a 3x3 matrix
*/
protected final float[] matrixBuffer = new float[9];
/**
* call this method to refresh the graph with a given matrix
*
* @param newMatrix
* @return
*/
public Matrix refresh(Matrix newMatrix, View chart, boolean invalidate) {
mMatrixTouch.set(newMatrix);
// make sure scale and translation are within their bounds
limitTransAndScale(mMatrixTouch, mContentRect);
if (invalidate)
chart.invalidate();
newMatrix.set(mMatrixTouch);
return newMatrix;
}
/**
* limits the maximum scale and X translation of the given matrix
*
* @param matrix
*/
public void limitTransAndScale(Matrix matrix, RectF content) {
matrix.getValues(matrixBuffer);
float curTransX = matrixBuffer[Matrix.MTRANS_X];
float curScaleX = matrixBuffer[Matrix.MSCALE_X];
float curTransY = matrixBuffer[Matrix.MTRANS_Y];
float curScaleY = matrixBuffer[Matrix.MSCALE_Y];
// min scale-x is 1f
mScaleX = Math.min(Math.max(mMinScaleX, curScaleX), mMaxScaleX);
// min scale-y is 1f
mScaleY = Math.min(Math.max(mMinScaleY, curScaleY), mMaxScaleY);
float width = 0f;
float height = 0f;
if (content != null) {
width = content.width();
height = content.height();
}
float maxTransX = -width * (mScaleX - 1f);
mTransX = Math.min(Math.max(curTransX, maxTransX - mTransOffsetX), mTransOffsetX);
float maxTransY = height * (mScaleY - 1f);
mTransY = Math.max(Math.min(curTransY, maxTransY + mTransOffsetY), -mTransOffsetY);
matrixBuffer[Matrix.MTRANS_X] = mTransX;
matrixBuffer[Matrix.MSCALE_X] = mScaleX;
matrixBuffer[Matrix.MTRANS_Y] = mTransY;
matrixBuffer[Matrix.MSCALE_Y] = mScaleY;
matrix.setValues(matrixBuffer);
}
/**
* Sets the minimum scale factor for the x-axis
*
* @param xScale
*/
public void setMinimumScaleX(float xScale) {
if (xScale < 1f)
xScale = 1f;
mMinScaleX = xScale;
limitTransAndScale(mMatrixTouch, mContentRect);
}
/**
* Sets the maximum scale factor for the x-axis
*
* @param xScale
*/
public void setMaximumScaleX(float xScale) {
if (xScale == 0.f)
xScale = Float.MAX_VALUE;
mMaxScaleX = xScale;
limitTransAndScale(mMatrixTouch, mContentRect);
}
/**
* Sets the minimum and maximum scale factors for the x-axis
*
* @param minScaleX
* @param maxScaleX
*/
public void setMinMaxScaleX(float minScaleX, float maxScaleX) {
if (minScaleX < 1f)
minScaleX = 1f;
if (maxScaleX == 0.f)
maxScaleX = Float.MAX_VALUE;
mMinScaleX = minScaleX;
mMaxScaleX = maxScaleX;
limitTransAndScale(mMatrixTouch, mContentRect);
}
/**
* Sets the minimum scale factor for the y-axis
*
* @param yScale
*/
public void setMinimumScaleY(float yScale) {
if (yScale < 1f)
yScale = 1f;
mMinScaleY = yScale;
limitTransAndScale(mMatrixTouch, mContentRect);
}
/**
* Sets the maximum scale factor for the y-axis
*
* @param yScale
*/
public void setMaximumScaleY(float yScale) {
if (yScale == 0.f)
yScale = Float.MAX_VALUE;
mMaxScaleY = yScale;
limitTransAndScale(mMatrixTouch, mContentRect);
}
public void setMinMaxScaleY(float minScaleY, float maxScaleY) {
if (minScaleY < 1f)
minScaleY = 1f;
if (maxScaleY == 0.f)
maxScaleY = Float.MAX_VALUE;
mMinScaleY = minScaleY;
mMaxScaleY = maxScaleY;
limitTransAndScale(mMatrixTouch, mContentRect);
}
/**
* Returns the charts-touch matrix used for translation and scale on touch.
*
* @return
*/
public Matrix getMatrixTouch() {
return mMatrixTouch;
}
/**
* ################ ################ ################ ################
*/
/**
* BELOW METHODS FOR BOUNDS CHECK
*/
public boolean isInBoundsX(float x) {
return isInBoundsLeft(x) && isInBoundsRight(x);
}
public boolean isInBoundsY(float y) {
return isInBoundsTop(y) && isInBoundsBottom(y);
}
public boolean isInBounds(float x, float y) {
return isInBoundsX(x) && isInBoundsY(y);
}
public boolean isInBoundsLeft(float x) {
return mContentRect.left <= x + 1;
}
public boolean isInBoundsRight(float x) {
x = (float) ((int) (x * 100.f)) / 100.f;
return mContentRect.right >= x - 1;
}
public boolean isInBoundsTop(float y) {
return mContentRect.top <= y;
}
public boolean isInBoundsBottom(float y) {
y = (float) ((int) (y * 100.f)) / 100.f;
return mContentRect.bottom >= y;
}
/**
* returns the current x-scale factor
*/
public float getScaleX() {
return mScaleX;
}
/**
* returns the current y-scale factor
*/
public float getScaleY() {
return mScaleY;
}
public float getMinScaleX() {
return mMinScaleX;
}
public float getMaxScaleX() {
return mMaxScaleX;
}
public float getMinScaleY() {
return mMinScaleY;
}
public float getMaxScaleY() {
return mMaxScaleY;
}
/**
* Returns the translation (drag / pan) distance on the x-axis
*
* @return
*/
public float getTransX() {
return mTransX;
}
/**
* Returns the translation (drag / pan) distance on the y-axis
*
* @return
*/
public float getTransY() {
return mTransY;
}
/**
* if the chart is fully zoomed out, return true
*
* @return
*/
public boolean isFullyZoomedOut() {
return isFullyZoomedOutX() && isFullyZoomedOutY();
}
/**
* Returns true if the chart is fully zoomed out on it's y-axis (vertical).
*
* @return
*/
public boolean isFullyZoomedOutY() {
return !(mScaleY > mMinScaleY || mMinScaleY > 1f);
}
/**
* Returns true if the chart is fully zoomed out on it's x-axis
* (horizontal).
*
* @return
*/
public boolean isFullyZoomedOutX() {
return !(mScaleX > mMinScaleX || mMinScaleX > 1f);
}
/**
* Set an offset in dp that allows the user to drag the chart over it's
* bounds on the x-axis.
*
* @param offset
*/
public void setDragOffsetX(float offset) {
mTransOffsetX = Utils.convertDpToPixel(offset);
}
/**
* Set an offset in dp that allows the user to drag the chart over it's
* bounds on the y-axis.
*
* @param offset
*/
public void setDragOffsetY(float offset) {
mTransOffsetY = Utils.convertDpToPixel(offset);
}
/**
* Returns true if both drag offsets (x and y) are zero or smaller.
*
* @return
*/
public boolean hasNoDragOffset() {
return mTransOffsetX <= 0 && mTransOffsetY <= 0;
}
/**
* Returns true if the chart is not yet fully zoomed out on the x-axis
*
* @return
*/
public boolean canZoomOutMoreX() {
return mScaleX > mMinScaleX;
}
/**
* Returns true if the chart is not yet fully zoomed in on the x-axis
*
* @return
*/
public boolean canZoomInMoreX() {
return mScaleX < mMaxScaleX;
}
/**
* Returns true if the chart is not yet fully zoomed out on the y-axis
*
* @return
*/
public boolean canZoomOutMoreY() {
return mScaleY > mMinScaleY;
}
/**
* Returns true if the chart is not yet fully zoomed in on the y-axis
*
* @return
*/
public boolean canZoomInMoreY() {
return mScaleY < mMaxScaleY;
}
}