blob: 7750e7f67f0617bce063127150211979820b34ec [file] [log] [blame]
package com.github.mikephil.charting.charts;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.github.mikephil.charting.components.Legend.LegendPosition;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.XAxis.XAxisPosition;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.components.YAxis.AxisDependency;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarLineScatterCandleData;
import com.github.mikephil.charting.data.BarLineScatterCandleDataSet;
import com.github.mikephil.charting.data.DataSet;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.data.filter.Approximator;
import com.github.mikephil.charting.interfaces.BarLineScatterCandleDataProvider;
import com.github.mikephil.charting.jobs.MoveViewJob;
import com.github.mikephil.charting.listener.BarLineChartTouchListener;
import com.github.mikephil.charting.listener.OnDrawListener;
import com.github.mikephil.charting.renderer.XAxisRenderer;
import com.github.mikephil.charting.renderer.YAxisRenderer;
import com.github.mikephil.charting.utils.FillFormatter;
import com.github.mikephil.charting.utils.Highlight;
import com.github.mikephil.charting.utils.PointD;
import com.github.mikephil.charting.utils.SelInfo;
import com.github.mikephil.charting.utils.Transformer;
import com.github.mikephil.charting.utils.Utils;
import java.util.ArrayList;
import java.util.List;
/**
* Base-class of LineChart, BarChart, ScatterChart and CandleStickChart.
*
* @author Philipp Jahoda
*/
@SuppressLint("RtlHardcoded")
public abstract class BarLineChartBase<T extends BarLineScatterCandleData<? extends BarLineScatterCandleDataSet<? extends Entry>>>
extends Chart<T> implements BarLineScatterCandleDataProvider {
/** the maximum number of entried to which values will be drawn */
protected int mMaxVisibleCount = 100;
/**
* flag that indicates if pinch-zoom is enabled. if true, both x and y axis
* can be scaled with 2 fingers, if false, x and y axis can be scaled
* separately
*/
protected boolean mPinchZoomEnabled = false;
/** flag that indicates if double tap zoom is enabled or not */
protected boolean mDoubleTapToZoomEnabled = true;
/**
* flag that indicates if highlighting per dragging over a fully zoomed out
* chart is enabled
*/
protected boolean mHighlightPerDragEnabled = true;
/**
* if set to true, the highlight indicator (lines for linechart, dark bar
* for barchart) will be drawn upon selecting values.
*/
protected boolean mHighLightIndicatorEnabled = true;
/** if true, dragging is enabled for the chart */
private boolean mDragEnabled = true;
private boolean mScaleXEnabled = true;
private boolean mScaleYEnabled = true;
/** if true, data filtering is enabled */
protected boolean mFilterData = false;
/** paint object for the (by default) lightgrey background of the grid */
protected Paint mGridBackgroundPaint;
protected Paint mBorderPaint;
/** flag indicating if the grid background should be drawn or not */
protected boolean mDrawGridBackground = true;
protected boolean mDrawBorders = false;
/** the listener for user drawing on the chart */
protected OnDrawListener mDrawListener;
/**
* the object representing the labels on the y-axis, this object is prepared
* in the pepareYLabels() method
*/
protected YAxis mAxisLeft;
protected YAxis mAxisRight;
/** the object representing the labels on the x-axis */
protected XAxis mXAxis;
protected YAxisRenderer mAxisRendererLeft;
protected YAxisRenderer mAxisRendererRight;
protected Transformer mLeftAxisTransformer;
protected Transformer mRightAxisTransformer;
protected XAxisRenderer mXAxisRenderer;
// /** the approximator object used for data filtering */
// private Approximator mApproximator;
public BarLineChartBase(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public BarLineChartBase(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BarLineChartBase(Context context) {
super(context);
}
@Override
protected void init() {
super.init();
mAxisLeft = new YAxis(AxisDependency.LEFT);
mAxisRight = new YAxis(AxisDependency.RIGHT);
mXAxis = new XAxis();
mLeftAxisTransformer = new Transformer(mViewPortHandler);
mRightAxisTransformer = new Transformer(mViewPortHandler);
mAxisRendererLeft = new YAxisRenderer(mViewPortHandler, mAxisLeft, mLeftAxisTransformer);
mAxisRendererRight = new YAxisRenderer(mViewPortHandler, mAxisRight, mRightAxisTransformer);
mXAxisRenderer = new XAxisRenderer(mViewPortHandler, mXAxis, mLeftAxisTransformer);
mListener = new BarLineChartTouchListener<BarLineChartBase<? extends BarLineScatterCandleData<? extends BarLineScatterCandleDataSet<? extends Entry>>>>(
this, mViewPortHandler.getMatrixTouch());
mGridBackgroundPaint = new Paint();
mGridBackgroundPaint.setStyle(Style.FILL);
// mGridBackgroundPaint.setColor(Color.WHITE);
mGridBackgroundPaint.setColor(Color.rgb(240, 240, 240)); // light
// grey
mBorderPaint = new Paint();
mBorderPaint.setStyle(Style.STROKE);
mBorderPaint.setColor(Color.BLACK);
mBorderPaint.setStrokeWidth(Utils.convertDpToPixel(1f));
}
// for performance tracking
private long totalTime = 0;
private long drawCycles = 0;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDataNotSet)
return;
long starttime = System.currentTimeMillis();calcModulus();
mXAxisRenderer.calcXBounds(this, mXAxis.mAxisLabelModulus);
mRenderer.calcXBounds(this, mXAxis.mAxisLabelModulus);
// execute all drawing commands
drawGridBackground(canvas);
if (mAxisLeft.isEnabled())
mAxisRendererLeft.computeAxis(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisMaximum);
if (mAxisRight.isEnabled())
mAxisRendererRight.computeAxis(mAxisRight.mAxisMinimum, mAxisRight.mAxisMaximum);
mXAxisRenderer.renderAxisLine(canvas);
mAxisRendererLeft.renderAxisLine(canvas);
mAxisRendererRight.renderAxisLine(canvas);
// make sure the graph values and grid cannot be drawn outside the
// content-rect
int clipRestoreCount = canvas.save();
canvas.clipRect(mViewPortHandler.getContentRect());
mXAxisRenderer.renderGridLines(canvas);
mAxisRendererLeft.renderGridLines(canvas);
mAxisRendererRight.renderGridLines(canvas);
if (mXAxis.isDrawLimitLinesBehindDataEnabled())
mXAxisRenderer.renderLimitLines(canvas);
if (mAxisLeft.isDrawLimitLinesBehindDataEnabled())
mAxisRendererLeft.renderLimitLines(canvas);
if (mAxisRight.isDrawLimitLinesBehindDataEnabled())
mAxisRendererRight.renderLimitLines(canvas);
mRenderer.drawData(canvas);
if (!mXAxis.isDrawLimitLinesBehindDataEnabled())
mXAxisRenderer.renderLimitLines(canvas);
if (!mAxisLeft.isDrawLimitLinesBehindDataEnabled())
mAxisRendererLeft.renderLimitLines(canvas);
if (!mAxisRight.isDrawLimitLinesBehindDataEnabled())
mAxisRendererRight.renderLimitLines(canvas);
// if highlighting is enabled
if (mHighlightEnabled && mHighLightIndicatorEnabled && valuesToHighlight())
mRenderer.drawHighlighted(canvas, mIndicesToHightlight);
// Removes clipping rectangle
canvas.restoreToCount(clipRestoreCount);
mRenderer.drawExtras(canvas);
mXAxisRenderer.renderAxisLabels(canvas);
mAxisRendererLeft.renderAxisLabels(canvas);
mAxisRendererRight.renderAxisLabels(canvas);
mRenderer.drawValues(canvas);
mLegendRenderer.renderLegend(canvas);
drawMarkers(canvas);
drawDescription(canvas);
if (mLogEnabled) {
long drawtime = (System.currentTimeMillis() - starttime);
totalTime += drawtime;
drawCycles += 1;
long average = totalTime / drawCycles;
Log.i(LOG_TAG, "Drawtime: " + drawtime + " ms, average: " + average + " ms, cycles: "
+ drawCycles);
}
}
/**
* RESET PERFORMANCE TRACKING FIELDS
*/
public void resetTracking() {
totalTime = 0;
drawCycles = 0;
}
protected void prepareValuePxMatrix() {
if (mLogEnabled)
Log.i(LOG_TAG, "Preparing Value-Px Matrix, xmin: " + mXChartMin + ", xmax: "
+ mXChartMax + ", xdelta: " + mDeltaX);
mRightAxisTransformer.prepareMatrixValuePx(mXChartMin, mDeltaX, mAxisRight.mAxisRange,
mAxisRight.mAxisMinimum);
mLeftAxisTransformer.prepareMatrixValuePx(mXChartMin, mDeltaX, mAxisLeft.mAxisRange,
mAxisLeft.mAxisMinimum);
}
protected void prepareOffsetMatrix() {
mRightAxisTransformer.prepareMatrixOffset(mAxisRight.isInverted());
mLeftAxisTransformer.prepareMatrixOffset(mAxisLeft.isInverted());
}
@Override
public void notifyDataSetChanged() {
if (mDataNotSet) {
if (mLogEnabled)
Log.i(LOG_TAG, "Preparing... DATA NOT SET.");
return;
} else {
if (mLogEnabled)
Log.i(LOG_TAG, "Preparing...");
}
if (mRenderer != null)
mRenderer.initBuffers();
calcMinMax();
if (mAxisLeft.needsDefaultFormatter())
mAxisLeft.setValueFormatter(mDefaultFormatter);
if (mAxisRight.needsDefaultFormatter())
mAxisRight.setValueFormatter(mDefaultFormatter);
mAxisRendererLeft.computeAxis(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisMaximum);
mAxisRendererRight.computeAxis(mAxisRight.mAxisMinimum, mAxisRight.mAxisMaximum);
mXAxisRenderer.computeAxis(mData.getXValAverageLength(), mData.getXVals());
mLegendRenderer.computeLegend(mData);
calculateOffsets();
}
@Override
protected void calcMinMax() {
float minLeft = mData.getYMin(AxisDependency.LEFT);
float maxLeft = mData.getYMax(AxisDependency.LEFT);
float minRight = mData.getYMin(AxisDependency.RIGHT);
float maxRight = mData.getYMax(AxisDependency.RIGHT);
float leftRange = Math.abs(maxLeft - (mAxisLeft.isStartAtZeroEnabled() ? 0 : minLeft));
float rightRange = Math.abs(maxRight - (mAxisRight.isStartAtZeroEnabled() ? 0 : minRight));
// in case all values are equal
if (leftRange == 0f) {
maxLeft = maxLeft + 1f;
if (!mAxisLeft.isStartAtZeroEnabled())
minLeft = minLeft - 1f;
}
if (rightRange == 0f) {
maxRight = maxRight + 1f;
if (!mAxisRight.isStartAtZeroEnabled())
minRight = minRight - 1f;
}
float topSpaceLeft = leftRange / 100f * mAxisLeft.getSpaceTop();
float topSpaceRight = rightRange / 100f * mAxisRight.getSpaceTop();
float bottomSpaceLeft = leftRange / 100f * mAxisLeft.getSpaceBottom();
float bottomSpaceRight = rightRange / 100f * mAxisRight.getSpaceBottom();
mXChartMax = mData.getXVals().size() - 1;
mDeltaX = Math.abs(mXChartMax - mXChartMin);
mAxisLeft.mAxisMaximum = !Float.isNaN(mAxisLeft.getAxisMaxValue()) ? mAxisLeft
.getAxisMaxValue() : maxLeft + topSpaceLeft;
mAxisRight.mAxisMaximum = !Float.isNaN(mAxisRight.getAxisMaxValue()) ? mAxisRight
.getAxisMaxValue() : maxRight + topSpaceRight;
mAxisLeft.mAxisMinimum = !Float.isNaN(mAxisLeft.getAxisMinValue()) ? mAxisLeft
.getAxisMinValue() : minLeft - bottomSpaceLeft;
mAxisRight.mAxisMinimum = !Float.isNaN(mAxisRight.getAxisMinValue()) ? mAxisRight
.getAxisMinValue() : minRight - bottomSpaceRight;
// consider starting at zero (0)
if (mAxisLeft.isStartAtZeroEnabled())
mAxisLeft.mAxisMinimum = 0f;
if (mAxisRight.isStartAtZeroEnabled())
mAxisRight.mAxisMinimum = 0f;
mAxisLeft.mAxisRange = Math.abs(mAxisLeft.mAxisMaximum - mAxisLeft.mAxisMinimum);
mAxisRight.mAxisRange = Math.abs(mAxisRight.mAxisMaximum - mAxisRight.mAxisMinimum);
}
@Override
protected void calculateOffsets() {
if (!mCustomViewPortEnabled) {
float offsetLeft = 0f, offsetRight = 0f, offsetTop = 0f, offsetBottom = 0f;
// setup offsets for legend
if (mLegend != null && mLegend.isEnabled()) {
if (mLegend.getPosition() == LegendPosition.RIGHT_OF_CHART
|| mLegend.getPosition() == LegendPosition.RIGHT_OF_CHART_CENTER) {
offsetRight += mLegend.mTextWidthMax + mLegend.getXOffset() * 2f;
} else if (mLegend.getPosition() == LegendPosition.LEFT_OF_CHART
|| mLegend.getPosition() == LegendPosition.LEFT_OF_CHART_CENTER) {
offsetLeft += mLegend.mTextWidthMax + mLegend.getXOffset() * 2f;
} else if (mLegend.getPosition() == LegendPosition.BELOW_CHART_LEFT
|| mLegend.getPosition() == LegendPosition.BELOW_CHART_RIGHT
|| mLegend.getPosition() == LegendPosition.BELOW_CHART_CENTER) {
offsetBottom += mLegend.mTextHeightMax * 3f;
}
}
// offsets for y-labels
if (mAxisLeft.needsOffset()) {
offsetLeft += mAxisLeft.getRequiredWidthSpace(mAxisRendererLeft
.getPaintAxisLabels());
}
if (mAxisRight.needsOffset()) {
offsetRight += mAxisRight.getRequiredWidthSpace(mAxisRendererRight
.getPaintAxisLabels());
}
float xlabelheight = mXAxis.mLabelHeight * 2f;
if (mXAxis.isEnabled()) {
// offsets for x-labels
if (mXAxis.getPosition() == XAxisPosition.BOTTOM) {
offsetBottom += xlabelheight;
} else if (mXAxis.getPosition() == XAxisPosition.TOP) {
offsetTop += xlabelheight;
} else if (mXAxis.getPosition() == XAxisPosition.BOTH_SIDED) {
offsetBottom += xlabelheight;
offsetTop += xlabelheight;
}
}
float min = Utils.convertDpToPixel(10f);
mViewPortHandler.restrainViewPort(Math.max(min, offsetLeft), Math.max(min, offsetTop),
Math.max(min, offsetRight), Math.max(min, offsetBottom));
if (mLogEnabled) {
Log.i(LOG_TAG, "offsetLeft: " + offsetLeft + ", offsetTop: " + offsetTop
+ ", offsetRight: " + offsetRight + ", offsetBottom: " + offsetBottom);
Log.i(LOG_TAG, "Content: " + mViewPortHandler.getContentRect().toString());
}
}
prepareOffsetMatrix();
prepareValuePxMatrix();
}
/**
* calculates the modulus for x-labels and grid
*/
protected void calcModulus() {
if (mXAxis == null || !mXAxis.isEnabled())
return;
if (!mXAxis.isAxisModulusCustom()) {
float[] values = new float[9];
mViewPortHandler.getMatrixTouch().getValues(values);
mXAxis.mAxisLabelModulus = (int) Math
.ceil((mData.getXValCount() * mXAxis.mLabelWidth)
/ (mViewPortHandler.contentWidth() * values[Matrix.MSCALE_X]));
}
if (mLogEnabled)
Log.i(LOG_TAG, "X-Axis modulus: " + mXAxis.mAxisLabelModulus + ", x-axis label width: "
+ mXAxis.mLabelWidth + ", content width: " + mViewPortHandler.contentWidth());
if (mXAxis.mAxisLabelModulus < 1)
mXAxis.mAxisLabelModulus = 1;
}
@Override
protected float[] getMarkerPosition(Entry e, int dataSetIndex) {
float xPos = e.getXIndex();
if (this instanceof BarChart) {
BarData bd = (BarData) mData;
float space = bd.getGroupSpace();
float j = mData.getDataSetByIndex(dataSetIndex)
.getEntryPosition(e);
float x = (j * (mData.getDataSetCount() - 1)) + dataSetIndex + space * j + space
/ 2f;
xPos += x;
}
// position of the marker depends on selected value index and value
float[] pts = new float[] {
xPos, e.getVal() * mAnimator.getPhaseY()
};
getTransformer(mData.getDataSetByIndex(dataSetIndex).getAxisDependency())
.pointValuesToPixel(pts);
return pts;
}
/**
* draws the grid background
*/
protected void drawGridBackground(Canvas c) {
if (mDrawGridBackground) {
// draw the grid background
c.drawRect(mViewPortHandler.getContentRect(), mGridBackgroundPaint);
}
if (mDrawBorders) {
c.drawRect(mViewPortHandler.getContentRect(), mBorderPaint);
}
}
/**
* Returns the Transformer class that contains all matrices and is
* responsible for transforming values into pixels on the screen and
* backwards.
*
* @return
*/
public Transformer getTransformer(AxisDependency which) {
if (which == AxisDependency.LEFT)
return mLeftAxisTransformer;
else
return mRightAxisTransformer;
}
/** touchlistener that handles touches and gestures on the chart */
protected OnTouchListener mListener;
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if (mListener == null || mDataNotSet)
return false;
// check if touch gestures are enabled
if (!mTouchEnabled)
return false;
else
return mListener.onTouch(this, event);
}
/**
* ################ ################ ################ ################
*/
/** CODE BELOW THIS RELATED TO SCALING AND GESTURES */
/**
* Zooms in by 1.4f, into the charts center. center.
*/
public void zoomIn() {
Matrix save = mViewPortHandler.zoomIn(getWidth() / 2f, -(getHeight() / 2f));
mViewPortHandler.refresh(save, this, true);
}
/**
* Zooms out by 0.7f, from the charts center. center.
*/
public void zoomOut() {
Matrix save = mViewPortHandler.zoomOut(getWidth() / 2f, -(getHeight() / 2f));
mViewPortHandler.refresh(save, this, true);
}
/**
* Zooms in or out by the given scale factor. x and y are the coordinates
* (in pixels) of the zoom center.
*
* @param scaleX if < 1f --> zoom out, if > 1f --> zoom in
* @param scaleY if < 1f --> zoom out, if > 1f --> zoom in
* @param x
* @param y
*/
public void zoom(float scaleX, float scaleY, float x, float y) {
Matrix save = mViewPortHandler.zoom(scaleX, scaleY, x, -y);
mViewPortHandler.refresh(save, this, true);
}
/**
* Resets all zooming and dragging and makes the chart fit exactly it's
* bounds.
*/
public void fitScreen() {
Matrix save = mViewPortHandler.fitScreen();
mViewPortHandler.refresh(save, this, true);
}
/**
* Sets the minimum scale value to which can be zoomed out. 1f = fitScreen
*
* @param scaleX
* @param scaleY
*/
public void setScaleMinima(float scaleX, float scaleY) {
mViewPortHandler.setMinimumScaleX(scaleX);
mViewPortHandler.setMinimumScaleY(scaleY);
}
/**
* Sets the size of the area (range on the x-axis) that should be maximum
* visible at once. If this is e.g. set to 10, no more than 10 values on the
* x-axis can be viewed at once without scrolling.
*
* @param xRange
*/
public void setVisibleXRange(float xRange) {
float xScale = mDeltaX / (xRange);
mViewPortHandler.setMinimumScaleX(xScale);
}
/**
* Sets the size of the area (range on the y-axis) that should be maximum
* visible at once.
*
* @param yRange
* @param axis - the axis for which this limit should apply
*/
public void setVisibleYRange(float yRange, AxisDependency axis) {
float yScale = getDeltaY(axis) / yRange;
mViewPortHandler.setMinimumScaleY(yScale);
}
/**
* Moves the left side of the current viewport to the specified x-index.
*
* @param xIndex
*/
public void moveViewToX(float xIndex) {
Runnable job = new MoveViewJob(mViewPortHandler, xIndex, 0f,
getTransformer(AxisDependency.LEFT), this);
if (mViewPortHandler.hasChartDimens()) {
post(job);
} else {
mJobs.add(job);
}
// float[] pts = new float[] {
// xIndex, 0f
// };
//
// getTransformer(AxisDependency.LEFT).pointValuesToPixel(pts);
//
// mViewPortHandler.centerViewPort(pts, this);
}
/**
* Centers the viewport to the specified y-value on the y-axis.
*
* @param yValue
* @param axis - which axis should be used as a reference for the y-axis
*/
public void moveViewToY(float yValue, AxisDependency axis) {
float valsInView = getDeltaY(axis) / mViewPortHandler.getScaleY();
Runnable job = new MoveViewJob(mViewPortHandler, 0f, yValue + valsInView / 2f,
getTransformer(axis), this);
if (mViewPortHandler.hasChartDimens()) {
post(job);
} else {
mJobs.add(job);
}
}
/**
* This will move the left side of the current viewport to the specified
* x-index on the x-axis, and center the viewport to the specified y-value
* on the y-axis.
*
* @param xIndex
* @param yValue
* @param axis - which axis should be used as a reference for the y-axis
*/
public void moveViewTo(float xIndex, float yValue, AxisDependency axis) {
float valsInView = getDeltaY(axis) / mViewPortHandler.getScaleY();
Runnable job = new MoveViewJob(mViewPortHandler, xIndex, yValue + valsInView / 2f,
getTransformer(axis), this);
if (mViewPortHandler.hasChartDimens()) {
post(job);
} else {
mJobs.add(job);
}
}
/**
* This will move the center of the current viewport to the specified
* x-index and y-value.
*
* @param xIndex
* @param yValue
* @param axis - which axis should be used as a reference for the y-axis
*/
public void centerViewTo(int xIndex, float yValue, AxisDependency axis) {
float valsInView = getDeltaY(axis) / mViewPortHandler.getScaleY();
float xsInView = getXAxis().getValues().size() / mViewPortHandler.getScaleX();
Runnable job = new MoveViewJob(mViewPortHandler,
xIndex - xsInView / 2f, yValue + valsInView / 2f,
getTransformer(axis), this);
if (mViewPortHandler.hasChartDimens()) {
post(job);
} else {
mJobs.add(job);
}
}
/** flag that indicates if a custom viewport offset has been set */
private boolean mCustomViewPortEnabled = false;
/**
* Sets custom offsets for the current ViewPort (the offsets on the sides of
* the actual chart window). Setting this will prevent the chart from
* automatically calculating it's offsets. Use resetViewPortOffsets() to
* undo this.
*
* @param left
* @param top
* @param right
* @param bottom
*/
public void setViewPortOffsets(final float left, final float top,
final float right, final float bottom) {
mCustomViewPortEnabled = true;
post(new Runnable() {
@Override
public void run() {
mViewPortHandler.restrainViewPort(left, top, right, bottom);
prepareOffsetMatrix();
prepareValuePxMatrix();
}
});
}
/**
* Resets all custom offsets set via setViewPortOffsets(...) method. Allows
* the chart to again calculate all offsets automatically.
*/
public void resetViewPortOffsets() {
mCustomViewPortEnabled = false;
calculateOffsets();
}
/**
* ################ ################ ################ ################
*/
/** CODE BELOW IS GETTERS AND SETTERS */
/**
* Returns the delta-y value (y-value range) of the specified axis.
*
* @param axis
* @return
*/
public float getDeltaY(AxisDependency axis) {
if (axis == AxisDependency.LEFT)
return mAxisLeft.mAxisRange;
else
return mAxisRight.mAxisRange;
}
/**
* set a new (e.g. custom) charttouchlistener NOTE: make sure to
* setTouchEnabled(true); if you need touch gestures on the chart
*
* @param l
*/
public void setOnTouchListener(OnTouchListener l) {
this.mListener = l;
}
/**
* Sets the OnDrawListener
*
* @param drawListener
*/
public void setOnDrawListener(OnDrawListener drawListener) {
this.mDrawListener = drawListener;
}
/**
* Gets the OnDrawListener. May be null.
*
* @return
*/
public OnDrawListener getDrawListener() {
return mDrawListener;
}
/**
* Returns the position (in pixels) the provided Entry has inside the chart
* view or null, if the provided Entry is null.
*
* @param e
* @return
*/
public PointF getPosition(Entry e, AxisDependency axis) {
if (e == null)
return null;
float[] vals = new float[] {
e.getXIndex(), e.getVal()
};
getTransformer(axis).pointValuesToPixel(vals);
return new PointF(vals[0], vals[1]);
}
/**
* sets the number of maximum visible drawn values on the chart only active
* when setDrawValues() is enabled
*
* @param count
*/
public void setMaxVisibleValueCount(int count) {
this.mMaxVisibleCount = count;
}
public int getMaxVisibleCount() {
return mMaxVisibleCount;
}
/**
* If set to true, the highlight indicator (vertical line for LineChart and
* ScatterChart, dark bar overlay for BarChart) that gives visual indication
* that an Entry has been selected will be drawn upon selecting values. This
* does not depend on the MarkerView. Default: true
*
* @param enabled
*/
public void setHighlightIndicatorEnabled(boolean enabled) {
mHighLightIndicatorEnabled = enabled;
}
/**
* Set this to true to allow highlighting per dragging over the chart
* surface when it is fully zoomed out. Default: true
*
* @param enabled
*/
public void setHighlightPerDragEnabled(boolean enabled) {
mHighlightPerDragEnabled = enabled;
}
public boolean isHighlightPerDragEnabled() {
return mHighlightPerDragEnabled;
}
/**
* Sets the color for the background of the chart-drawing area (everything
* behind the grid lines).
*
* @param color
*/
public void setGridBackgroundColor(int color) {
mGridBackgroundPaint.setColor(color);
}
/**
* Set this to true to enable dragging (moving the chart with the finger)
* for the chart (this does not effect scaling).
*
* @param enabled
*/
public void setDragEnabled(boolean enabled) {
this.mDragEnabled = enabled;
}
/**
* Returns true if dragging is enabled for the chart, false if not.
*
* @return
*/
public boolean isDragEnabled() {
return mDragEnabled;
}
/**
* Set this to true to enable scaling (zooming in and out by gesture) for
* the chart (this does not effect dragging) on both X- and Y-Axis.
*
* @param enabled
*/
public void setScaleEnabled(boolean enabled) {
this.mScaleXEnabled = enabled;
this.mScaleYEnabled = enabled;
}
public void setScaleXEnabled(boolean enabled) {
mScaleXEnabled = enabled;
}
public void setScaleYEnabled(boolean enabled) {
mScaleYEnabled = enabled;
}
public boolean isScaleXEnabled() {
return mScaleXEnabled;
}
public boolean isScaleYEnabled() {
return mScaleYEnabled;
}
/**
* Set this to true to enable zooming in by double-tap on the chart.
* Default: enabled
*
* @param enabled
*/
public void setDoubleTapToZoomEnabled(boolean enabled) {
mDoubleTapToZoomEnabled = enabled;
}
/**
* Returns true if zooming via double-tap is enabled false if not.
*
* @return
*/
public boolean isDoubleTapToZoomEnabled() {
return mDoubleTapToZoomEnabled;
}
/**
* set this to true to draw the grid background, false if not
*
* @param enabled
*/
public void setDrawGridBackground(boolean enabled) {
mDrawGridBackground = enabled;
}
/**
* Sets drawing the borders rectangle to true. If this is enabled, there is
* no point drawing the axis-lines of x- and y-axis.
*
* @param enabled
*/
public void setDrawBorders(boolean enabled) {
mDrawBorders = enabled;
}
/**
* Sets the width of the border lines in dp.
*
* @param width
*/
public void setBorderWidth(float width) {
mBorderPaint.setStrokeWidth(Utils.convertDpToPixel(width));
}
/**
* Sets the color of the chart border lines.
*
* @param color
*/
public void setBorderColor(int color) {
mBorderPaint.setColor(color);
}
/**
* Returns the Highlight object (contains x-index and DataSet index) of the
* selected value at the given touch point inside the Line-, Scatter-, or
* CandleStick-Chart.
*
* @param x
* @param y
* @return
*/
public Highlight getHighlightByTouchPoint(float x, float y) {
if (mDataNotSet || mData == null) {
Log.e(LOG_TAG, "Can't select by touch. No data set.");
return null;
}
// create an array of the touch-point
float[] pts = new float[2];
pts[0] = x;
// take any transformer to determine the x-axis value
mLeftAxisTransformer.pixelsToValue(pts);
double xTouchVal = pts[0];
double base = Math.floor(xTouchVal);
double touchOffset = mDeltaX * 0.025;
// touch out of chart
if (xTouchVal < -touchOffset || xTouchVal > mDeltaX + touchOffset)
return null;
if (base < 0)
base = 0;
if (base >= mDeltaX)
base = mDeltaX - 1;
int xIndex = (int) base;
// check if we are more than half of a x-value or not
if (xTouchVal - base > 0.5) {
xIndex = (int) base + 1;
}
List<SelInfo> valsAtIndex = getYValsAtIndex(xIndex);
float leftdist = Utils.getMinimumDistance(valsAtIndex, y, AxisDependency.LEFT);
float rightdist = Utils.getMinimumDistance(valsAtIndex, y, AxisDependency.RIGHT);
if (mData.getFirstRight() == null)
rightdist = Float.MAX_VALUE;
if (mData.getFirstLeft() == null)
leftdist = Float.MAX_VALUE;
AxisDependency axis = leftdist < rightdist ? AxisDependency.LEFT : AxisDependency.RIGHT;
int dataSetIndex = Utils.getClosestDataSetIndex(valsAtIndex, y, axis);
if (dataSetIndex == -1)
return null;
return new Highlight(xIndex, dataSetIndex);
}
/**
* Returns an array of SelInfo objects for the given x-index. The SelInfo
* objects give information about the value at the selected index and the
* DataSet it belongs to. INFORMATION: This method does calculations at
* runtime. Do not over-use in performance critical situations.
*
* @return
*/
public List<SelInfo> getYValsAtIndex(int xIndex) {
List<SelInfo> vals = new ArrayList<SelInfo>();
float[] pts = new float[2];
for (int i = 0; i < mData.getDataSetCount(); i++) {
DataSet<?> dataSet = mData.getDataSetByIndex(i);
// extract all y-values from all DataSets at the given x-index
float yVal = dataSet.getYValForXIndex(xIndex);
pts[1] = yVal;
getTransformer(dataSet.getAxisDependency()).pointValuesToPixel(pts);
if (!Float.isNaN(pts[1])) {
vals.add(new SelInfo(pts[1], i, dataSet));
}
}
return vals;
}
/**
* Returns the x and y values in the chart at the given touch point
* (encapsulated in a PointD). This method transforms pixel coordinates to
* coordinates / values in the chart. This is the opposite method to
* getPixelsForValues(...).
*
* @param x
* @param y
* @return
*/
public PointD getValuesByTouchPoint(float x, float y, AxisDependency axis) {
// create an array of the touch-point
float[] pts = new float[2];
pts[0] = x;
pts[1] = y;
getTransformer(axis).pixelsToValue(pts);
double xTouchVal = pts[0];
double yTouchVal = pts[1];
return new PointD(xTouchVal, yTouchVal);
}
/**
* Transforms the given chart values into pixels. This is the opposite
* method to getValuesByTouchPoint(...).
*
* @param x
* @param y
* @return
*/
public PointD getPixelsForValues(float x, float y, AxisDependency axis) {
float[] pts = new float[] {
x, y
};
getTransformer(axis).pointValuesToPixel(pts);
return new PointD(pts[0], pts[1]);
}
/**
* returns the y-value at the given touch position (must not necessarily be
* a value contained in one of the datasets)
*
* @param x
* @param y
* @return
*/
public float getYValueByTouchPoint(float x, float y, AxisDependency axis) {
return (float) getValuesByTouchPoint(x, y, axis).y;
}
/**
* returns the Entry object displayed at the touched position of the chart
*
* @param x
* @param y
* @return
*/
public Entry getEntryByTouchPoint(float x, float y) {
Highlight h = getHighlightByTouchPoint(x, y);
if (h != null) {
return mData.getEntryForHighlight(h);
}
return null;
}
/**
* returns the DataSet object displayed at the touched position of the chart
*
* @param x
* @param y
* @return
*/
public BarLineScatterCandleDataSet<? extends Entry> getDataSetByTouchPoint(float x, float y) {
Highlight h = getHighlightByTouchPoint(x, y);
if (h != null) {
return mData.getDataSetByIndex(h.getDataSetIndex());
}
return null;
}
/**
* Returns the lowest x-index (value on the x-axis) that is still visible on
* the chart.
*
* @return
*/
@Override
public int getLowestVisibleXIndex() {
float[] pts = new float[] {
mViewPortHandler.contentLeft(), mViewPortHandler.contentBottom()
};
getTransformer(AxisDependency.LEFT).pixelsToValue(pts);
return (pts[0] <= 0) ? 0 : (int) (pts[0] + 1);
}
/**
* Returns the highest x-index (value on the x-axis) that is still visible
* on the chart.
*
* @return
*/
@Override
public int getHighestVisibleXIndex() {
float[] pts = new float[] {
mViewPortHandler.contentRight(), mViewPortHandler.contentBottom()
};
getTransformer(AxisDependency.LEFT).pixelsToValue(pts);
return (pts[0] >= mData.getXValCount()) ? mData.getXValCount() - 1 : (int) pts[0];
}
/**
* returns the current x-scale factor
*/
public float getScaleX() {
return mViewPortHandler.getScaleX();
}
/**
* returns the current y-scale factor
*/
public float getScaleY() {
return mViewPortHandler.getScaleY();
}
/**
* if the chart is fully zoomed out, return true
*
* @return
*/
public boolean isFullyZoomedOut() {
return mViewPortHandler.isFullyZoomedOut();
}
/**
* Returns the left y-axis object. In the horizontal bar-chart, this is the
* top axis.
*
* @return
*/
public YAxis getAxisLeft() {
return mAxisLeft;
}
/**
* Returns the right y-axis object. In the horizontal bar-chart, this is the
* bottom axis.
*
* @return
*/
public YAxis getAxisRight() {
return mAxisRight;
}
/**
* Returns the y-axis object to the corresponding AxisDependency. In the
* horizontal bar-chart, LEFT == top, RIGHT == BOTTOM
*
* @param axis
* @return
*/
public YAxis getAxis(AxisDependency axis) {
if (axis == AxisDependency.LEFT)
return mAxisLeft;
else
return mAxisRight;
}
@Override
public boolean isInverted(AxisDependency axis) {
return getAxis(axis).isInverted();
}
/**
* Returns the object representing all x-labels, this method can be used to
* acquire the XAxis object and modify it (e.g. change the position of the
* labels)
*
* @return
*/
public XAxis getXAxis() {
return mXAxis;
}
/**
* Enables data filtering for the chart data, filtering will use the user
* customized Approximator handed over to this method.
*
* @param a
*/
public void enableFiltering(Approximator a) {
mFilterData = true;
// mApproximator = a;
}
/**
* Disables data filtering for the chart.
*/
public void disableFiltering() {
mFilterData = false;
}
/**
* returns true if data filtering is enabled, false if not
*
* @return
*/
public boolean isFilteringEnabled() {
return mFilterData;
}
/**
* if set to true, both x and y axis can be scaled with 2 fingers, if false,
* x and y axis can be scaled separately. default: false
*
* @param enabled
*/
public void setPinchZoom(boolean enabled) {
mPinchZoomEnabled = enabled;
}
/**
* returns true if pinch-zoom is enabled, false if not
*
* @return
*/
public boolean isPinchZoomEnabled() {
return mPinchZoomEnabled;
}
/**
* 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) {
mViewPortHandler.setDragOffsetX(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) {
mViewPortHandler.setDragOffsetY(offset);
}
/**
* Returns true if both drag offsets (x and y) are zero or smaller.
*
* @return
*/
public boolean hasNoDragOffset() {
return mViewPortHandler.hasNoDragOffset();
}
public XAxisRenderer getRendererXAxis() {
return mXAxisRenderer;
}
public YAxisRenderer getRendererLeftYAxis() {
return mAxisRendererLeft;
}
public YAxisRenderer getRendererRightYAxis() {
return mAxisRendererRight;
}
public float getYChartMax() {
return Math.max(mAxisLeft.mAxisMaximum, mAxisRight.mAxisMaximum);
}
public float getYChartMin() {
return Math.min(mAxisLeft.mAxisMinimum, mAxisRight.mAxisMinimum);
}
/**
* Returns true if either the left or the right or both axes are inverted.
*
* @return
*/
public boolean isAnyAxisInverted() {
if (mAxisLeft.isInverted())
return true;
if (mAxisRight.isInverted())
return true;
return false;
}
@Override
public void setPaint(Paint p, int which) {
super.setPaint(p, which);
switch (which) {
case PAINT_GRID_BACKGROUND:
mGridBackgroundPaint = p;
break;
}
}
@Override
public Paint getPaint(int which) {
Paint p = super.getPaint(which);
if (p != null)
return p;
switch (which) {
case PAINT_GRID_BACKGROUND:
return mGridBackgroundPaint;
}
return null;
}
/**
* Default formatter that calculates the position of the filled line.
*
* @author Philipp Jahoda
*/
protected class DefaultFillFormatter implements FillFormatter {
@Override
public float getFillLinePosition(LineDataSet dataSet, LineData data,
float chartMaxY, float chartMinY) {
float fillMin = 0f;
if (dataSet.getYMax() > 0 && dataSet.getYMin() < 0) {
fillMin = 0f;
} else {
if (!getAxis(dataSet.getAxisDependency()).isStartAtZeroEnabled()) {
float max, min;
if (data.getYMax() > 0)
max = 0f;
else
max = chartMaxY;
if (data.getYMin() < 0)
min = 0f;
else
min = chartMinY;
fillMin = dataSet.getYMin() >= 0 ? min : max;
} else {
fillMin = 0f;
}
}
return fillMin;
}
}
}