| |
| package com.github.mikephil.charting.charts; |
| |
| import android.content.Context; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.Matrix; |
| import android.graphics.Paint; |
| import android.graphics.Paint.Align; |
| import android.graphics.Paint.Style; |
| import android.graphics.Path; |
| import android.graphics.Rect; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.MotionEvent; |
| import android.view.ViewParent; |
| |
| import com.github.mikephil.charting.data.BarLineScatterCandleRadarData; |
| import com.github.mikephil.charting.data.ChartData; |
| import com.github.mikephil.charting.data.Entry; |
| import com.github.mikephil.charting.data.filter.Approximator; |
| import com.github.mikephil.charting.interfaces.OnDrawListener; |
| import com.github.mikephil.charting.listener.BarLineChartTouchListener; |
| import com.github.mikephil.charting.utils.Highlight; |
| import com.github.mikephil.charting.utils.Legend.LegendPosition; |
| import com.github.mikephil.charting.utils.LimitLine; |
| import com.github.mikephil.charting.utils.PointD; |
| import com.github.mikephil.charting.utils.SelInfo; |
| import com.github.mikephil.charting.utils.Utils; |
| import com.github.mikephil.charting.utils.XLabels; |
| import com.github.mikephil.charting.utils.XLabels.XLabelPosition; |
| import com.github.mikephil.charting.utils.YLabels; |
| import com.github.mikephil.charting.utils.YLabels.YLabelPosition; |
| |
| import java.text.DecimalFormat; |
| import java.util.ArrayList; |
| |
| /** |
| * Baseclass of LineChart and BarChart. |
| * |
| * @author Philipp Jahoda |
| */ |
| public abstract class BarLineChartBase extends Chart { |
| |
| /** if set to true, the y-axis is inverted and low values start at the top */ |
| private boolean mInvertYAxis = false; |
| |
| /** the maximum number of entried to which values will be drawn */ |
| protected int mMaxVisibleCount = 100; |
| |
| /** minimum scale value on the y-axis */ |
| private float mMinScaleY = 1f; |
| |
| /** minimum scale value on the x-axis */ |
| private float mMinScaleX = 1f; |
| |
| /** contains the current scale factor of the x-axis */ |
| protected float mScaleX = 1f; |
| |
| /** contains the current scale factor of the y-axis */ |
| protected float mScaleY = 1f; |
| |
| /** holds the maximum scale factor of the y-axis, default 10f */ |
| protected float mMaxScaleY = 10f; |
| |
| /** the width of the grid lines */ |
| protected float mGridWidth = 1f; |
| |
| /** |
| * 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; |
| |
| /** if true, dragging / scaling is enabled for the chart */ |
| protected boolean mDragScaleEnabled = true; |
| |
| /** if true, the y range is predefined */ |
| protected boolean mFixedYValues = false; |
| |
| /** if true, the y-label entries will always start at zero */ |
| protected boolean mStartAtZero = true; |
| |
| /** if true, data filtering is enabled */ |
| protected boolean mFilterData = false; |
| |
| /** paint object for the grid lines */ |
| protected Paint mGridPaint; |
| |
| /** paint object for the (by default) lightgrey background of the grid */ |
| protected Paint mGridBackgroundPaint; |
| |
| /** paint for the line surrounding the chart */ |
| protected Paint mBorderPaint; |
| |
| /** paint for the x-label values */ |
| protected Paint mXLabelPaint; |
| |
| /** paint for the y-label values */ |
| protected Paint mYLabelPaint; |
| |
| /** paint used for the limit lines */ |
| protected Paint mLimitLinePaint; |
| |
| /** |
| * if set to true, the highlight indicator (lines for linechart, dark bar |
| * for barchart) will be drawn upon selecting values. |
| */ |
| protected boolean mHighLightIndicatorEnabled = true; |
| |
| /** |
| * boolean to indicate if user drawing on chart should automatically be |
| * finished |
| */ |
| protected boolean mAutoFinishDrawing; |
| |
| /** flag indicating if the vertical grid should be drawn or not */ |
| protected boolean mDrawVerticalGrid = true; |
| |
| /** flag indicating if the horizontal grid should be drawn or not */ |
| protected boolean mDrawHorizontalGrid = true; |
| |
| /** flag indicating if the y-labels should be drawn or not */ |
| protected boolean mDrawYLabels = true; |
| |
| /** flag indicating if the x-labels should be drawn or not */ |
| protected boolean mDrawXLabels = true; |
| |
| /** flag indicating if the chart border rectangle should be drawn or not */ |
| protected boolean mDrawBorder = true; |
| |
| /** flag indicating if the grid background should be drawn or not */ |
| protected boolean mDrawGridBackground = true; |
| |
| /** 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 YLabels mYLabels = new YLabels(); |
| |
| /** the object representing the labels on the x-axis */ |
| protected XLabels mXLabels = new XLabels(); |
| |
| /** 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(); |
| |
| mListener = new BarLineChartTouchListener(this, mMatrixTouch); |
| |
| mXLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG); |
| mXLabelPaint.setColor(Color.BLACK); |
| mXLabelPaint.setTextAlign(Align.CENTER); |
| mXLabelPaint.setTextSize(Utils.convertDpToPixel(10f)); |
| |
| mYLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG); |
| mYLabelPaint.setColor(Color.BLACK); |
| mYLabelPaint.setTextSize(Utils.convertDpToPixel(10f)); |
| |
| mGridPaint = new Paint(); |
| mGridPaint.setColor(Color.GRAY); |
| mGridPaint.setStrokeWidth(mGridWidth); |
| mGridPaint.setStyle(Style.STROKE); |
| mGridPaint.setAlpha(90); |
| |
| mBorderPaint = new Paint(); |
| mBorderPaint.setColor(Color.BLACK); |
| mBorderPaint.setStrokeWidth(mGridWidth * 2f); |
| mBorderPaint.setStyle(Style.STROKE); |
| |
| mGridBackgroundPaint = new Paint(); |
| mGridBackgroundPaint.setStyle(Style.FILL); |
| // mGridBackgroundPaint.setColor(Color.WHITE); |
| mGridBackgroundPaint.setColor(Color.rgb(240, 240, 240)); // light |
| // grey |
| |
| mLimitLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); |
| mLimitLinePaint.setStyle(Paint.Style.STROKE); |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| super.onDraw(canvas); |
| |
| if (mDataNotSet) |
| return; |
| |
| long starttime = System.currentTimeMillis(); |
| |
| // if data filtering is enabled |
| if (mFilterData) { |
| mCurrentData = getFilteredData(); |
| |
| Log.i(LOG_TAG, "FilterTime: " + (System.currentTimeMillis() - |
| starttime) + " ms"); |
| starttime = System.currentTimeMillis(); |
| } else { |
| mCurrentData = getDataOriginal(); |
| // Log.i(LOG_TAG, "Filtering disabled."); |
| } |
| |
| if (mXLabels.isAdjustXLabelsEnabled()) |
| calcModulus(); |
| |
| // execute all drawing commands |
| drawGridBackground(); |
| drawBorder(); |
| |
| prepareYLabels(); |
| |
| // make sure the graph values and grid cannot be drawn outside the |
| // content-rect |
| int clipRestoreCount = mDrawCanvas.save(); |
| mDrawCanvas.clipRect(mContentRect); |
| |
| drawHorizontalGrid(); |
| drawVerticalGrid(); |
| |
| drawData(); |
| |
| drawLimitLines(); |
| |
| // if highlighting is enabled |
| if (mHighlightEnabled && mHighLightIndicatorEnabled && valuesToHighlight()) |
| drawHighlights(); |
| |
| // Removes clipping rectangle |
| mDrawCanvas.restoreToCount(clipRestoreCount); |
| |
| drawAdditional(); |
| |
| drawXLabels(); |
| |
| drawYLabels(); |
| |
| drawValues(); |
| |
| drawLegend(); |
| |
| drawMarkers(); |
| |
| drawDescription(); |
| |
| canvas.drawBitmap(mDrawBitmap, 0, 0, mDrawPaint); |
| |
| Log.i(LOG_TAG, "DrawTime: " + (System.currentTimeMillis() - starttime) + " ms"); |
| } |
| |
| /** |
| * does all necessary preparations, needed when data is changed or flags |
| * that effect the data are changed |
| */ |
| @Override |
| public void prepare() { |
| |
| if (mDataNotSet) |
| return; |
| |
| calcMinMax(mFixedYValues); |
| |
| prepareXLabels(); |
| |
| // calculate how many digits are needed |
| calcFormats(); |
| |
| prepareLegend(); |
| } |
| |
| @Override |
| public void notifyDataSetChanged() { |
| if (!mFixedYValues) { |
| prepare(); |
| } else { |
| calcMinMax(mFixedYValues); |
| } |
| } |
| |
| @Override |
| protected void calculateOffsets() { |
| |
| if (mLegend == null) |
| return; |
| |
| // setup offsets for legend |
| if (mLegend.getPosition() == LegendPosition.RIGHT_OF_CHART) { |
| |
| // this is the space between the legend and the chart |
| float spacing = Utils.convertDpToPixel(7f); |
| mLegend.setOffsetRight(mLegend.getMaximumEntryLength(mLegendLabelPaint) |
| + mLegend.getFormSize() + mLegend.getFormToTextSpace() + spacing); |
| mLegendLabelPaint.setTextAlign(Align.LEFT); |
| |
| } else if (mLegend.getPosition() == LegendPosition.BELOW_CHART_LEFT |
| || mLegend.getPosition() == LegendPosition.BELOW_CHART_RIGHT |
| || mLegend.getPosition() == LegendPosition.BELOW_CHART_CENTER) { |
| |
| if (mXLabels.getPosition() == XLabelPosition.TOP) |
| mLegend.setOffsetBottom(mLegendLabelPaint.getTextSize() * 3.5f); |
| else { |
| mLegend.setOffsetBottom(mLegendLabelPaint.getTextSize() * 2.5f); |
| } |
| } |
| |
| float yleft = 0f, yright = 0f; |
| float xtop = 0f, xbottom = 0f; |
| |
| // offsets for y-labels |
| if (mYLabels.getPosition() == YLabelPosition.LEFT) { |
| |
| if (mYChartMin >= 0) |
| yleft = Utils.calcTextWidth(mYLabelPaint, (int) mDeltaY + ".00" + mUnit); |
| else |
| yleft = Utils.calcTextWidth(mYLabelPaint, (int) (mDeltaY * -1) + ".00" + mUnit); |
| |
| mYLabelPaint.setTextAlign(Align.RIGHT); |
| |
| } else if (mYLabels.getPosition() == YLabelPosition.RIGHT) { |
| |
| if (mYChartMin >= 0) |
| yright = Utils.calcTextWidth(mYLabelPaint, (int) mDeltaY + ".00" + mUnit); |
| else |
| yright = Utils.calcTextWidth(mYLabelPaint, (int) (mDeltaY * -1) + ".00" + mUnit); |
| |
| mYLabelPaint.setTextAlign(Align.LEFT); |
| |
| } else if (mYLabels.getPosition() == YLabelPosition.BOTH_SIDED) { |
| |
| float width = 0f; |
| |
| if (mYChartMin >= 0) |
| width = Utils.calcTextWidth(mYLabelPaint, (int) mDeltaY + ".00" + mUnit); |
| else |
| width = Utils.calcTextWidth(mYLabelPaint, (int) (mDeltaY * -1) + ".00" + mUnit); |
| |
| yright = width; |
| yleft = width; |
| } |
| |
| // offsets for x-labels |
| if (mXLabels.getPosition() == XLabelPosition.BOTTOM) { |
| |
| xbottom = Utils.calcTextHeight(mXLabelPaint, "Q") * 2f; |
| |
| } else if (mXLabels.getPosition() == XLabelPosition.TOP) { |
| |
| xtop = Utils.calcTextHeight(mXLabelPaint, "Q") * 2f; |
| |
| } else if (mXLabels.getPosition() == XLabelPosition.BOTH_SIDED) { |
| |
| float height = Utils.calcTextHeight(mXLabelPaint, "Q") * 2f; |
| xbottom = height; |
| xtop = height; |
| } |
| |
| if (mDrawLegend) { |
| |
| if (mDrawXLabels) { |
| mOffsetBottom = Math.max(mOffsetBottom, xbottom + mLegend.getOffsetBottom()); |
| mOffsetTop = Math.max(mOffsetTop, xtop + mLegend.getOffsetTop()); |
| } else { |
| mOffsetBottom = Math.max(mOffsetBottom, mLegend.getOffsetBottom()); |
| mOffsetTop = Math.max(mOffsetTop, mLegend.getOffsetTop()); |
| } |
| |
| if (mDrawYLabels) { |
| // merge legend, label and chart offsets |
| mOffsetLeft = Math.max(mOffsetLeft, yleft + mLegend.getOffsetLeft()); |
| mOffsetRight = Math.max(mOffsetRight, yright + mLegend.getOffsetRight()); |
| } else { |
| mOffsetLeft = Math.max(mOffsetLeft, mLegend.getOffsetLeft()); |
| mOffsetRight = Math.max(mOffsetRight, mLegend.getOffsetRight()); |
| } |
| |
| } else { |
| |
| if (mDrawXLabels) { |
| mOffsetBottom = Math.max(mOffsetBottom, xbottom); |
| mOffsetTop = Math.max(mOffsetTop, xtop); |
| } |
| |
| if (mDrawYLabels) { |
| // merge chart and label offsets |
| mOffsetLeft = Math.max(mOffsetLeft, yleft); |
| mOffsetRight = Math.max(mOffsetRight, yright); |
| } |
| } |
| |
| // Log.i(LOG_TAG, "left: " + mOffsetLeft + ", right: " + mOffsetRight + |
| // ", top: " + mOffsetTop |
| // + ", bottom: " + mOffsetBottom); |
| |
| // those offsets are equal for legend and other chart, just apply them |
| mLegend.setOffsetTop(mOffsetTop); |
| mLegend.setOffsetLeft(mOffsetLeft); |
| |
| prepareContentRect(); |
| |
| float scaleX = (float) ((getWidth() - mOffsetLeft - mOffsetRight) / mDeltaX); |
| float scaleY = (float) ((getHeight() - mOffsetBottom - mOffsetTop) / mDeltaY); |
| |
| Matrix val = new Matrix(); |
| val.postTranslate(0, -mYChartMin); |
| val.postScale(scaleX, -scaleY); |
| |
| mMatrixValueToPx.set(val); |
| |
| Matrix offset = new Matrix(); |
| // offset.postTranslate(mOffsetLeft, getHeight() - mOffsetBottom); |
| |
| if (!mInvertYAxis) |
| offset.postTranslate(mOffsetLeft, getHeight() - mOffsetBottom); |
| else { |
| offset.setTranslate(mOffsetLeft, -getOffsetTop()); |
| offset.postScale(1.0f, -1.0f); |
| } |
| |
| mMatrixOffset.set(offset); |
| } |
| |
| /** |
| * Calculates the offsets that belong to the legend, this method is only |
| * relevant when drawing into the chart. It can be used to refresh the |
| * legend. |
| */ |
| public void calculateLegendOffsets() { |
| |
| // setup offsets for legend |
| if (mLegend.getPosition() == LegendPosition.RIGHT_OF_CHART) { |
| |
| mLegend.setOffsetRight(mLegend.getMaximumEntryLength(mLegendLabelPaint)); |
| mLegendLabelPaint.setTextAlign(Align.LEFT); |
| |
| } else if (mLegend.getPosition() == LegendPosition.BELOW_CHART_LEFT |
| || mLegend.getPosition() == LegendPosition.BELOW_CHART_RIGHT) { |
| |
| if (mXLabels.getPosition() == XLabelPosition.TOP) |
| mLegend.setOffsetBottom(mLegendLabelPaint.getTextSize() * 3.5f); |
| else { |
| mLegend.setOffsetBottom(mLegendLabelPaint.getTextSize() * 2.5f); |
| } |
| } |
| } |
| |
| /** |
| * calculates the modulus for x-labels and grid |
| */ |
| protected void calcModulus() { |
| |
| float[] values = new float[9]; |
| mMatrixTouch.getValues(values); |
| |
| mXLabels.mXAxisLabelModulus = (int) Math |
| .ceil((mCurrentData.getXValCount() * mXLabels.mLabelWidth) |
| / (mContentRect.width() * values[Matrix.MSCALE_X])); |
| } |
| |
| /** the decimalformat responsible for formatting the values in the chart */ |
| protected DecimalFormat mFormatValue = null; |
| |
| /** the number of digits the y-labels are formatted with */ |
| protected int mYLabelFormatDigits = -1; |
| |
| /** |
| * calculates the required number of digits for the y-labels and for the |
| * values that might be drawn in the chart (if enabled) |
| */ |
| protected void calcFormats() { |
| |
| if (mValueDigitsToUse == -1) |
| mValueFormatDigits = Utils.getFormatDigits(mDeltaY); |
| else |
| mValueFormatDigits = mValueDigitsToUse; |
| |
| StringBuffer b = new StringBuffer(); |
| for (int i = 0; i < mValueFormatDigits; i++) { |
| if (i == 0) |
| b.append("."); |
| b.append("0"); |
| } |
| |
| mFormatValue = new DecimalFormat("###,###,###,##0" + b.toString()); |
| } |
| |
| @Override |
| protected void calcMinMax(boolean fixedValues) { |
| super.calcMinMax(fixedValues); // calc min and max in the super class |
| |
| // additional handling for space (default 15% space) |
| // float space = Math.abs(mDeltaY / 100f * 15f); |
| float space = Math.abs(Math.max(Math.abs(mYChartMax), Math.abs(mYChartMin)) / 100f * 15f); |
| |
| if (mStartAtZero) { |
| |
| if (mYChartMax < 0) { |
| mYChartMax = 0; |
| // calc delta |
| mYChartMin = mYChartMin - space; |
| } else { |
| mYChartMin = 0; |
| // calc delta |
| mYChartMax = mYChartMax + space; |
| } |
| } else { |
| |
| mYChartMin = mYChartMin - space / 2f; |
| mYChartMax = mYChartMax + space / 2f; |
| } |
| |
| mDeltaY = Math.abs(mYChartMax - mYChartMin); |
| } |
| |
| /** |
| * setup the x-axis labels |
| */ |
| protected void prepareXLabels() { |
| |
| StringBuffer a = new StringBuffer(); |
| |
| // float length = (int) (((float) |
| // (mCurrentData.getXVals().get(0).length() + mCurrentData |
| // .getXVals() |
| // .get(mCurrentData.getXValCount() - 1) |
| // .length()))); |
| |
| int max = (int) Math.round(mCurrentData.getXValAverageLength() |
| + mXLabels.getSpaceBetweenLabels()); |
| |
| for (int i = 0; i < max; i++) { |
| a.append("h"); |
| } |
| |
| mXLabels.mLabelWidth = Utils.calcTextWidth(mXLabelPaint, a.toString()); |
| mXLabels.mLabelHeight = Utils.calcTextWidth(mXLabelPaint, "Q"); |
| } |
| |
| /** |
| * Sets up the y-axis labels. Computes the desired number of labels between |
| * the two given extremes. Unlike the papareXLabels() method, this method |
| * needs to be called upon every refresh of the view. |
| * |
| * @return |
| */ |
| private void prepareYLabels() { |
| |
| // calculate the currently visible extremes |
| PointD p1 = getValuesByTouchPoint(mContentRect.left, mContentRect.top); |
| PointD p2 = getValuesByTouchPoint(mContentRect.left, mContentRect.bottom); |
| |
| // mYChartMin = (float) p2.y; |
| // mYChartMax = (float) p1.y; |
| |
| if (!mInvertYAxis) { |
| mYChartMin = (float) p2.y; |
| mYChartMax = (float) p1.y; |
| } else { |
| |
| if (!mStartAtZero) |
| mYChartMin = (float) Math.min(p1.y, p2.y); |
| else |
| mYChartMin = 0; |
| mYChartMax = (float) Math.max(p1.y, p2.y); |
| } |
| |
| float yMin = mYChartMin; |
| float yMax = mYChartMax; |
| |
| int labelCount = mYLabels.getLabelCount(); |
| double range = yMax - yMin; |
| |
| if (labelCount == 0 || range <= 0) { |
| mYLabels.mEntries = new float[] {}; |
| mYLabels.mEntryCount = 0; |
| return; |
| } |
| |
| double rawInterval = range / labelCount; |
| double interval = Utils.roundToNextSignificant(rawInterval); |
| double intervalMagnitude = Math.pow(10, (int) Math.log10(interval)); |
| int intervalSigDigit = (int) (interval / intervalMagnitude); |
| if (intervalSigDigit > 5) { |
| // Use one order of magnitude higher, to avoid intervals like 0.9 or |
| // 90 |
| interval = Math.floor(10 * intervalMagnitude); |
| } |
| |
| double first = Math.ceil(yMin / interval) * interval; |
| double last = Utils.nextUp(Math.floor(yMax / interval) * interval); |
| |
| double f; |
| int i; |
| int n = 0; |
| for (f = first; f <= last; f += interval) { |
| ++n; |
| } |
| |
| mYLabels.mEntryCount = n; |
| |
| if (mYLabels.mEntries.length < n) { |
| // Ensure stops contains at least numStops elements. |
| mYLabels.mEntries = new float[n]; |
| } |
| |
| for (f = first, i = 0; i < n; f += interval, ++i) { |
| mYLabels.mEntries[i] = (float) f; |
| } |
| |
| if (interval < 1) { |
| mYLabels.mDecimals = (int) Math.ceil(-Math.log10(interval)); |
| } else { |
| mYLabels.mDecimals = 0; |
| } |
| } |
| |
| /** |
| * draws the x-axis labels to the screen depending on their position |
| */ |
| private void drawXLabels() { |
| |
| if (!mDrawXLabels) |
| return; |
| |
| float yoffset = Utils.convertDpToPixel(3.5f); |
| |
| mXLabelPaint.setTypeface(mXLabels.getTypeface()); |
| mXLabelPaint.setTextSize(mXLabels.getTextSize()); |
| |
| if (mXLabels.getPosition() == XLabelPosition.TOP) { |
| |
| drawXLabels(getOffsetTop() - yoffset); |
| |
| } else if (mXLabels.getPosition() == XLabelPosition.BOTTOM) { |
| |
| drawXLabels(getHeight() - mOffsetBottom + mXLabels.mLabelHeight + yoffset * 1.5f); |
| |
| } else { // BOTH SIDED |
| |
| drawXLabels(getOffsetTop() - 7); |
| drawXLabels(getHeight() - mOffsetBottom + mXLabels.mLabelHeight + yoffset * 1.6f); |
| } |
| } |
| |
| /** |
| * draws the x-labels on the specified y-position |
| * |
| * @param yPos |
| */ |
| protected void drawXLabels(float yPos) { |
| |
| // pre allocate to save performance (dont allocate in loop) |
| float[] position = new float[] { |
| 0f, 0f |
| }; |
| |
| for (int i = 0; i < mCurrentData.getXValCount(); i += mXLabels.mXAxisLabelModulus) { |
| |
| position[0] = i; |
| |
| // center the text |
| if (mXLabels.isCenterXLabelsEnabled()) |
| position[0] += 0.5f; |
| |
| transformPointArray(position); |
| |
| if (position[0] >= mOffsetLeft && position[0] <= getWidth() - mOffsetRight) { |
| |
| String label = mCurrentData.getXVals().get(i); |
| |
| if (mXLabels.isAvoidFirstLastClippingEnabled()) { |
| |
| // avoid clipping of the last |
| if (i == mCurrentData.getXValCount() - 1) { |
| float width = Utils.calcTextWidth(mXLabelPaint, label); |
| |
| if (width > getOffsetRight() * 2 && position[0] + width > getWidth()) |
| position[0] -= width / 2; |
| |
| // avoid clipping of the first |
| } else if (i == 0) { |
| |
| float width = Utils.calcTextWidth(mXLabelPaint, label); |
| position[0] += width / 2; |
| } |
| } |
| |
| mDrawCanvas.drawText(label, position[0], |
| yPos, |
| mXLabelPaint); |
| } |
| } |
| } |
| |
| /** |
| * draws the y-axis labels to the screen |
| */ |
| private void drawYLabels() { |
| |
| if (!mDrawYLabels) |
| return; |
| |
| float[] positions = new float[mYLabels.mEntryCount * 2]; |
| |
| for (int i = 0; i < positions.length; i += 2) { |
| // only fill y values, x values are not needed since the y-labels |
| // are |
| // static on the x-axis |
| positions[i + 1] = mYLabels.mEntries[i / 2]; |
| } |
| |
| transformPointArray(positions); |
| |
| float xoffset = Utils.convertDpToPixel(5f); |
| float yoffset = Utils.calcTextHeight(mYLabelPaint, "A") / 2.5f; |
| |
| mYLabelPaint.setTypeface(mYLabels.getTypeface()); |
| mYLabelPaint.setTextSize(mYLabels.getTextSize()); |
| |
| // determine position and draw adequately |
| if (mYLabels.getPosition() == YLabelPosition.LEFT) { |
| |
| mYLabelPaint.setTextAlign(Align.RIGHT); |
| drawYLabels(mOffsetLeft - xoffset, positions, yoffset); |
| |
| } else if (mYLabels.getPosition() == YLabelPosition.RIGHT) { |
| |
| mYLabelPaint.setTextAlign(Align.LEFT); |
| drawYLabels(getWidth() - mOffsetRight + xoffset, positions, yoffset); |
| |
| } else { // BOTH SIDED Y-AXIS LABELS |
| |
| // draw left legend |
| mYLabelPaint.setTextAlign(Align.RIGHT); |
| drawYLabels(mOffsetLeft - xoffset, positions, yoffset); |
| |
| // draw right legend |
| mYLabelPaint.setTextAlign(Align.LEFT); |
| drawYLabels(getWidth() - mOffsetRight + xoffset, positions, yoffset); |
| } |
| } |
| |
| /** |
| * draws the y-labels on the specified x-position |
| * |
| * @param xPos |
| * @param positions |
| */ |
| private void drawYLabels(float xPos, float[] positions, float yOffset) { |
| |
| // draw |
| for (int i = 0; i < mYLabels.mEntryCount; i++) { |
| |
| String text = Utils.formatNumber(mYLabels.mEntries[i], mYLabels.mDecimals, |
| mSeparateTousands); |
| |
| if (!mYLabels.isDrawTopYLabelEntryEnabled() && i >= mYLabels.mEntryCount - 1) |
| return; |
| |
| if (mYLabels.isDrawUnitsInYLabelEnabled()) { |
| mDrawCanvas.drawText(text + mUnit, xPos, positions[i * 2 + 1] + yOffset, |
| mYLabelPaint); |
| } else { |
| mDrawCanvas.drawText(text, xPos, positions[i * 2 + 1] + yOffset, mYLabelPaint); |
| } |
| } |
| } |
| |
| /** enums for all different border styles */ |
| public enum BorderPosition { |
| LEFT, RIGHT, TOP, BOTTOM |
| } |
| |
| /** |
| * array that holds positions where to draw the chart border lines |
| */ |
| private BorderPosition[] mBorderPositions = new BorderPosition[] { |
| BorderPosition.BOTTOM |
| }; |
| |
| /** |
| * draws a line that surrounds the chart |
| */ |
| protected void drawBorder() { |
| |
| if (!mDrawBorder || mBorderPositions == null) |
| return; |
| |
| for (int i = 0; i < mBorderPositions.length; i++) { |
| |
| switch (mBorderPositions[i]) { |
| case LEFT: |
| mDrawCanvas.drawLine(mOffsetLeft, mOffsetTop, mOffsetLeft, getHeight() |
| - mOffsetBottom, mBorderPaint); |
| break; |
| case RIGHT: |
| mDrawCanvas.drawLine(getWidth() - mOffsetRight, mOffsetTop, getWidth() |
| - mOffsetRight, getHeight() |
| - mOffsetBottom, mBorderPaint); |
| break; |
| case TOP: |
| mDrawCanvas.drawLine(mOffsetLeft, mOffsetTop, getWidth() - mOffsetRight, |
| mOffsetTop, mBorderPaint); |
| break; |
| case BOTTOM: |
| mDrawCanvas.drawLine(mOffsetLeft, getHeight() |
| - mOffsetBottom, getWidth() - mOffsetRight, getHeight() |
| - mOffsetBottom, mBorderPaint); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * draws the grid background |
| */ |
| protected void drawGridBackground() { |
| |
| if (!mDrawGridBackground) |
| return; |
| |
| Rect gridBackground = new Rect((int) mOffsetLeft + 1, (int) mOffsetTop + 1, getWidth() |
| - (int) mOffsetRight, |
| getHeight() - (int) mOffsetBottom); |
| |
| // draw the grid background |
| mDrawCanvas.drawRect(gridBackground, mGridBackgroundPaint); |
| } |
| |
| /** |
| * draws the horizontal grid |
| */ |
| protected void drawHorizontalGrid() { |
| |
| if (!mDrawHorizontalGrid) |
| return; |
| |
| // create a new path object only once and use reset() instead of |
| // unnecessary allocations |
| Path p = new Path(); |
| |
| // draw the horizontal grid |
| for (int i = 0; i < mYLabels.mEntryCount; i++) { |
| |
| p.reset(); |
| p.moveTo(0, mYLabels.mEntries[i]); |
| p.lineTo(mDeltaX, mYLabels.mEntries[i]); |
| |
| transformPath(p); |
| |
| mDrawCanvas.drawPath(p, mGridPaint); |
| } |
| } |
| |
| /** |
| * draws the vertical grid |
| */ |
| protected void drawVerticalGrid() { |
| |
| if (!mDrawVerticalGrid || mCurrentData == null) |
| return; |
| |
| float[] position = new float[] { |
| 0f, 0f |
| }; |
| |
| for (int i = 0; i < mCurrentData.getXValCount(); i += mXLabels.mXAxisLabelModulus) { |
| |
| position[0] = i; |
| |
| transformPointArray(position); |
| |
| if (position[0] >= mOffsetLeft && position[0] <= getWidth()) { |
| |
| mDrawCanvas.drawLine(position[0], mOffsetTop, position[0], getHeight() |
| - mOffsetBottom, mGridPaint); |
| } |
| } |
| } |
| |
| /** |
| * Draws the limit lines if there are one. |
| */ |
| private void drawLimitLines() { |
| |
| ArrayList<LimitLine> limitLines = ((BarLineScatterCandleRadarData) mOriginalData) |
| .getLimitLines(); |
| |
| if (limitLines == null) |
| return; |
| |
| // pre allocate to save performance |
| float[] pts = new float[] { |
| 0, 0, 0, 0 |
| }; |
| |
| for (int i = 0; i < limitLines.size(); i++) { |
| |
| LimitLine l = limitLines.get(i); |
| |
| pts[0] = 0f; |
| pts[1] = l.getLimit(); |
| pts[2] = mDeltaX; |
| pts[3] = l.getLimit(); |
| |
| transformPointArray(pts); |
| |
| mLimitLinePaint.setColor(l.getLineColor()); |
| mLimitLinePaint.setPathEffect(l.getDashPathEffect()); |
| mLimitLinePaint.setStrokeWidth(l.getLineWidth()); |
| |
| mDrawCanvas.drawLine(pts[0], pts[1], pts[2], pts[3], mLimitLinePaint); |
| } |
| } |
| |
| /** |
| * returns true if the specified point (x-axis) exceeds the limits of what |
| * is visible to the right side |
| * |
| * @param v |
| * @return |
| */ |
| protected boolean isOffContentRight(float p) { |
| if (p > mContentRect.right) |
| return true; |
| else |
| return false; |
| } |
| |
| /** |
| * returns true if the specified point (x-axis) exceeds the limits of what |
| * is visible to the left side |
| * |
| * @param v |
| * @return |
| */ |
| protected boolean isOffContentLeft(float p) { |
| if (p < mContentRect.left) |
| return true; |
| else |
| return false; |
| } |
| |
| /** |
| * returns true if the specified point (y-axis) exceeds the limits of what |
| * is visible on the top |
| * |
| * @param v |
| * @return |
| */ |
| protected boolean isOffContentTop(float p) { |
| if (p < mContentRect.top) |
| return true; |
| else |
| return false; |
| } |
| |
| /** |
| * returns true if the specified point (y-axis) exceeds the limits of what |
| * is visible on the bottom |
| * |
| * @param v |
| * @return |
| */ |
| protected boolean isOffContentBottom(float p) { |
| if (p > mContentRect.bottom) |
| return true; |
| else |
| return false; |
| } |
| |
| /** touchlistener that handles touches and gestures on the chart */ |
| protected OnTouchListener mListener; |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent 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 */ |
| |
| /** |
| * disables intercept touchevents |
| */ |
| public void disableScroll() { |
| ViewParent parent = getParent(); |
| parent.requestDisallowInterceptTouchEvent(true); |
| } |
| |
| /** |
| * enables intercept touchevents |
| */ |
| public void enableScroll() { |
| ViewParent parent = getParent(); |
| parent.requestDisallowInterceptTouchEvent(false); |
| } |
| |
| /** |
| * Zooms in by 1.4f, x and y are the coordinates (in pixels) of the zoom |
| * center. |
| * |
| * @param x |
| * @param y |
| */ |
| public void zoomIn(float x, float y) { |
| |
| Matrix save = new Matrix(); |
| save.set(mMatrixTouch); |
| |
| save.postScale(1.4f, 1.4f, x, y); |
| |
| refreshTouch(save); |
| } |
| |
| /** |
| * Zooms out by 0.7f, x and y are the coordinates (in pixels) of the zoom |
| * center. |
| */ |
| public void zoomOut(float x, float y) { |
| |
| Matrix save = new Matrix(); |
| save.set(mMatrixTouch); |
| |
| save.postScale(0.7f, 0.7f, x, y); |
| |
| refreshTouch(save); |
| } |
| |
| /** |
| * 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 = new Matrix(); |
| save.set(mMatrixTouch); |
| |
| // Log.i(LOG_TAG, "Zooming, x: " + x + ", y: " + y); |
| |
| save.postScale(scaleX, scaleY, x, -y); |
| |
| refreshTouch(save); |
| } |
| |
| /** |
| * If this is set to true, the y-axis is inverted which means that low |
| * values are on top of the chart, high values on bottom. |
| * |
| * @param enabled |
| */ |
| public void setInvertYAxisEnabled(boolean enabled) { |
| mInvertYAxis = enabled; |
| } |
| |
| /** |
| * If this returns true, the y-axis is inverted. |
| * |
| * @return |
| */ |
| public boolean isInvertYAxisEnabled() { |
| return mInvertYAxis; |
| } |
| |
| /** |
| * Centers the viewport around the specified x-index and the specified |
| * 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. First set the scale minima, then center the |
| * viewport. SHOULD BE CALLED AFTER setting data for the chart. |
| * |
| * @param xIndex the index on the x-axis to center to |
| * @param yVal the value ont he y-axis to center to |
| */ |
| public synchronized void centerViewPort(final int xIndex, final float yVal) { |
| |
| // the post makes it possible that this call waits until the view has |
| // finisted setting up |
| post(new Runnable() { |
| |
| @Override |
| public void run() { |
| |
| float indicesInView = mDeltaX / mScaleX; |
| float valsInView = mDeltaY / mScaleY; |
| |
| // Log.i(LOG_TAG, "indices: " + indicesInView + ", vals: " + |
| // valsInView); |
| |
| float[] pts = new float[] { |
| xIndex - indicesInView / 2f, yVal + valsInView / 2f |
| }; |
| |
| Matrix save = new Matrix(); |
| save.set(mMatrixTouch); |
| |
| transformPointArray(pts); |
| |
| final float x = -pts[0] + getOffsetLeft(); |
| final float y = -pts[1] - getOffsetTop(); |
| |
| save.postTranslate(x, y); |
| |
| refreshTouch(save); |
| |
| // Log.i(LOG_TAG, "ViewPort centered, xIndex: " + xIndex + |
| // ", yVal: " + yVal |
| // + ", transX: " + x + ", transY: " + y); |
| } |
| }); |
| } |
| |
| /** |
| * call this method to refresh the graph with a given touch matrix |
| * |
| * @param newTouchMatrix |
| * @return |
| */ |
| public Matrix refreshTouch(Matrix newTouchMatrix) { |
| |
| mMatrixTouch.set(newTouchMatrix); |
| |
| // make sure scale and translation are within their bounds |
| limitTransAndScale(mMatrixTouch); |
| |
| // redraw |
| invalidate(); |
| |
| newTouchMatrix.set(mMatrixTouch); |
| return newTouchMatrix; |
| } |
| |
| /** |
| * call this method to refresh the graph with a given touch matrix without |
| * calling invalidate() |
| * |
| * @param newTouchMatrix |
| * @return |
| */ |
| public Matrix refreshTouchNoInvalidate(Matrix newTouchMatrix) { |
| |
| mMatrixTouch.set(newTouchMatrix); |
| |
| // make sure scale and translation are within their bounds |
| limitTransAndScale(mMatrixTouch); |
| |
| newTouchMatrix.set(mMatrixTouch); |
| return newTouchMatrix; |
| } |
| |
| /** |
| * limits the maximum scale and X translation of the given matrix |
| * |
| * @param matrix |
| */ |
| protected void limitTransAndScale(Matrix matrix) { |
| |
| float[] vals = new float[9]; |
| matrix.getValues(vals); |
| |
| float curTransX = vals[Matrix.MTRANS_X]; |
| float curScaleX = vals[Matrix.MSCALE_X]; |
| |
| float curTransY = vals[Matrix.MTRANS_Y]; |
| float curScaleY = vals[Matrix.MSCALE_Y]; |
| |
| // Log.i(LOG_TAG, "curTransX: " + curTransX + ", curScaleX: " + |
| // curScaleX); |
| // Log.i(LOG_TAG, "curTransY: " + curTransY + ", curScaleY: " + |
| // curScaleY); |
| |
| // min scale-x is 1f |
| mScaleX = Math.max(mMinScaleX, Math.min(getMaxScaleX(), curScaleX)); |
| |
| // min scale-y is 1f |
| mScaleY = Math.max(mMinScaleY, Math.min(getMaxScaleY(), curScaleY)); |
| |
| if (mContentRect == null) |
| return; |
| |
| float maxTransX = -(float) mContentRect.width() * (mScaleX - 1f); |
| float newTransX = Math.min(Math.max(curTransX, maxTransX), 0f); |
| |
| float maxTransY = (float) mContentRect.height() * (mScaleY - 1f); |
| float newTransY = Math.max(Math.min(curTransY, maxTransY), 0f); |
| |
| // Log.i(LOG_TAG, "scale-X: " + mScaleX + ", maxTransX: " + maxTransX + |
| // ", newTransX: " |
| // + newTransX); |
| // Log.i(LOG_TAG, "scale-Y: " + mScaleY + ", maxTransY: " + maxTransY + |
| // ", newTransY: " |
| // + newTransY); |
| |
| vals[Matrix.MTRANS_X] = newTransX; |
| vals[Matrix.MSCALE_X] = mScaleX; |
| |
| vals[Matrix.MTRANS_Y] = newTransY; |
| vals[Matrix.MSCALE_Y] = mScaleY; |
| |
| matrix.setValues(vals); |
| } |
| |
| /** |
| * ################ ################ ################ ################ |
| */ |
| /** CODE BELOW IS GETTERS AND SETTERS */ |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * set if the user should be allowed to draw onto the chart |
| * |
| * @param drawingEnabled |
| */ |
| public void setDrawingEnabled(boolean drawingEnabled) { |
| if (mListener instanceof BarLineChartTouchListener) { |
| ((BarLineChartTouchListener) mListener).setDrawingEnabled(drawingEnabled); |
| } |
| } |
| |
| /** |
| * Set to true to auto finish user drawing. THis means that the value that |
| * has been drawn into the chart is filled up to the maximum x-index |
| * automatically. |
| * |
| * @param enabled |
| */ |
| public void setAutoFinish(boolean enabled) { |
| this.mAutoFinishDrawing = enabled; |
| } |
| |
| /** |
| * True if auto finish user drawing is enabled |
| * |
| * @return |
| */ |
| public boolean isAutoFinishEnabled() { |
| return mAutoFinishDrawing; |
| } |
| |
| /** |
| * Gets the OnDrawListener. May be null. |
| * |
| * @return |
| */ |
| public OnDrawListener getDrawListener() { |
| return mDrawListener; |
| } |
| |
| /** |
| * Sets the minimum scale values for both axes. This limits the extent to |
| * which the user can zoom-out. Scale 0.5f means 0.5x zoom (zoomed out by |
| * factor 2), scale 0.1f means maximum zoomed out by factor 10, scale 2f |
| * means the user cannot zoom out further than 2x zoom, ... |
| * |
| * @param scaleXmin |
| * @param scaleYmin |
| */ |
| public void setScaleMinima(float scaleXmin, float scaleYmin) { |
| |
| mMinScaleX = scaleXmin; |
| mMinScaleY = scaleYmin; |
| |
| zoom(mMinScaleX, mMinScaleY, 0f, 0f); |
| } |
| |
| /** |
| * Sets the effective range of y-values the chart can display. If this is |
| * set, the y-range is fixed and cannot be changed. This means, no |
| * recalculation of the bounds of the chart concerning the y-axis will be |
| * done when adding new data. To disable this, provide Float.NaN as a |
| * parameter or call resetYRange(); |
| * |
| * @param minY |
| * @param maxY |
| * @param invalidate if set to true, the chart will redraw itself after |
| * calling this method |
| */ |
| public void setYRange(float minY, float maxY, boolean invalidate) { |
| |
| if (Float.isNaN(minY) || Float.isNaN(maxY)) { |
| resetYRange(invalidate); |
| return; |
| } |
| |
| mFixedYValues = true; |
| |
| mYChartMin = minY; |
| mYChartMax = maxY; |
| if (minY < 0) { |
| mStartAtZero = false; |
| } |
| mDeltaY = mYChartMax - mYChartMin; |
| |
| calcFormats(); |
| prepareMatrix(); |
| if (invalidate) |
| invalidate(); |
| } |
| |
| /** |
| * Resets the previously set y range. If new data is added, the y-range will |
| * be recalculated. |
| * |
| * @param invalidate if set to true, the chart will redraw itself after |
| * calling this method |
| */ |
| public void resetYRange(boolean invalidate) { |
| mFixedYValues = false; |
| calcMinMax(mFixedYValues); |
| |
| prepareMatrix(); |
| if (invalidate) |
| invalidate(); |
| } |
| |
| /** |
| * if this returns true, the chart has a fixed range on the y-axis that is |
| * not dependant on the actual data in the chart |
| * |
| * @return |
| */ |
| public boolean hasFixedYValues() { |
| return mFixedYValues; |
| } |
| |
| /** |
| * sets the color for the grid lines |
| * |
| * @param color |
| */ |
| public void setGridColor(int color) { |
| mGridPaint.setColor(color); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * If set to true, the highlight indicators (cross of two lines for |
| * LineChart and ScatterChart, dark bar overlay for BarChart) that give |
| * 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; |
| } |
| |
| /** |
| * enable this to force the y-axis labels to always start at zero |
| * |
| * @param enabled |
| */ |
| public void setStartAtZero(boolean enabled) { |
| this.mStartAtZero = enabled; |
| prepare(); |
| prepareMatrix(); |
| calculateOffsets(); |
| } |
| |
| /** |
| * returns true if the chart is set to start at zero, false otherwise |
| * |
| * @return |
| */ |
| public boolean isStartAtZeroEnabled() { |
| return mStartAtZero; |
| } |
| |
| /** |
| * sets the width of the grid lines (min 0.1f, max = 3f) |
| * |
| * @param width |
| */ |
| public void setGridWidth(float width) { |
| |
| if (width < 0.1f) |
| width = 0.1f; |
| if (width > 3.0f) |
| width = 3.0f; |
| mGridWidth = width; |
| } |
| |
| /** |
| * set this to true to enable dragging / scaling for the chart |
| * |
| * @param enabled |
| */ |
| public void setDragScaleEnabled(boolean enabled) { |
| this.mDragScaleEnabled = enabled; |
| } |
| |
| /** |
| * returns true if dragging / scaling is enabled for the chart, false if not |
| * |
| * @return |
| */ |
| public boolean isDragScaleEnabled() { |
| return mDragScaleEnabled; |
| } |
| |
| /** |
| * if set to true, the vertical grid will be drawn, default: true |
| * |
| * @param enabled |
| */ |
| public void setDrawVerticalGrid(boolean enabled) { |
| mDrawVerticalGrid = enabled; |
| } |
| |
| /** |
| * if set to true, the horizontal grid will be drawn, default: true |
| * |
| * @param enabled |
| */ |
| public void setDrawHorizontalGrid(boolean enabled) { |
| mDrawHorizontalGrid = enabled; |
| } |
| |
| /** |
| * returns true if drawing the vertical grid is enabled, false if not |
| * |
| * @return |
| */ |
| public boolean isDrawVerticalGridEnabled() { |
| return mDrawVerticalGrid; |
| } |
| |
| /** |
| * returns true if drawing the horizontal grid is enabled, false if not |
| * |
| * @return |
| */ |
| public boolean isDrawHorizontalGridEnabled() { |
| return mDrawHorizontalGrid; |
| } |
| |
| /** |
| * set this to true to draw the border surrounding the chart, default: true |
| * |
| * @param enabled |
| */ |
| public void setDrawBorder(boolean enabled) { |
| mDrawBorder = enabled; |
| } |
| |
| /** |
| * set this to true to draw the grid background, false if not |
| * |
| * @param enabled |
| */ |
| public void setDrawGridBackground(boolean enabled) { |
| mDrawGridBackground = enabled; |
| } |
| |
| /** |
| * set this to true to enable drawing the x-labels, false if not |
| * |
| * @param enabled |
| */ |
| public void setDrawXLabels(boolean enabled) { |
| mDrawXLabels = enabled; |
| } |
| |
| /** |
| * set this to true to enable drawing the y-labels, false if not |
| * |
| * @param enabled |
| */ |
| public void setDrawYLabels(boolean enabled) { |
| mDrawYLabels = enabled; |
| } |
| |
| /** |
| * Sets an array of positions where to draw the chart border lines (e.g. new |
| * BorderStyle[] { BorderStyle.BOTTOM }) |
| * |
| * @param styles |
| */ |
| public void setBorderPositions(BorderPosition[] styles) { |
| mBorderPositions = styles; |
| } |
| |
| /** |
| * Returns the array of positions where the chart-border is drawn. |
| * |
| * @return |
| */ |
| public BorderPosition[] getBorderPositions() { |
| return mBorderPositions; |
| } |
| |
| /** |
| * 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) { |
| 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; |
| pts[1] = y; |
| |
| Matrix tmp = new Matrix(); |
| |
| // invert all matrixes to convert back to the original value |
| mMatrixOffset.invert(tmp); |
| tmp.mapPoints(pts); |
| |
| mMatrixTouch.invert(tmp); |
| tmp.mapPoints(pts); |
| |
| mMatrixValueToPx.invert(tmp); |
| tmp.mapPoints(pts); |
| |
| double xTouchVal = pts[0]; |
| double yTouchVal = pts[1]; |
| double base = Math.floor(xTouchVal); |
| |
| double touchOffset = mDeltaX * 0.025; |
| // Log.i(LOG_TAG, "touchindex x: " + xTouchVal + ", touchindex y: " + |
| // yTouchVal + ", offset: " |
| // + touchOffset); |
| // Toast.makeText(getContext(), "touchindex x: " + xTouchVal + |
| // ", touchindex y: " + yTouchVal + ", offset: " + touchOffset, |
| // Toast.LENGTH_SHORT).show(); |
| |
| // touch out of chart |
| if (xTouchVal < -touchOffset || xTouchVal > mDeltaX + touchOffset) |
| return null; |
| |
| if (this instanceof CandleStickChart) |
| base -= 0.5; |
| |
| if (base < 0) |
| base = 0; |
| if (base >= mDeltaX) |
| base = mDeltaX - 1; |
| |
| int xIndex = (int) base; |
| |
| int dataSetIndex = 0; // index of the DataSet inside the ChartData |
| // object |
| |
| // check if we are more than half of a x-value or not |
| if (xTouchVal - base > 0.5) { |
| xIndex = (int) base + 1; |
| } |
| |
| ArrayList<SelInfo> valsAtIndex = getYValsAtIndex(xIndex); |
| |
| dataSetIndex = getClosestDataSetIndex(valsAtIndex, (float) yTouchVal); |
| |
| if (dataSetIndex == -1) |
| return null; |
| |
| // Toast.makeText(getContext(), "xindex: " + xIndex + ", dataSetIndex: " |
| // + dataSetIndex, |
| // Toast.LENGTH_SHORT).show(); |
| |
| return new Highlight(xIndex, dataSetIndex); |
| } |
| |
| /** |
| * Returns the index of the DataSet that contains the closest value on the |
| * y-axis. |
| * |
| * @param valsAtIndex all the values at a specific index |
| * @return |
| */ |
| private int getClosestDataSetIndex(ArrayList<SelInfo> valsAtIndex, float val) { |
| |
| int index = -1; |
| float distance = Float.MAX_VALUE; |
| |
| for (int i = 0; i < valsAtIndex.size(); i++) { |
| |
| float cdistance = Math.abs((float) valsAtIndex.get(i).val - val); |
| if (cdistance < distance) { |
| index = valsAtIndex.get(i).dataSetIndex; |
| distance = cdistance; |
| } |
| } |
| |
| // Log.i(LOG_TAG, "Closest DataSet index: " + index); |
| |
| return index; |
| } |
| |
| /** |
| * 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) { |
| |
| // create an array of the touch-point |
| float[] pts = new float[2]; |
| pts[0] = x; |
| pts[1] = y; |
| |
| Matrix tmp = new Matrix(); |
| |
| // invert all matrixes to convert back to the original value |
| mMatrixOffset.invert(tmp); |
| tmp.mapPoints(pts); |
| |
| mMatrixTouch.invert(tmp); |
| tmp.mapPoints(pts); |
| |
| mMatrixValueToPx.invert(tmp); |
| tmp.mapPoints(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) { |
| |
| float[] pts = new float[] { |
| x, y |
| }; |
| |
| transformPointArray(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) { |
| return (float) getValuesByTouchPoint(x, y).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 mCurrentData.getEntryForHighlight(h); |
| } |
| return null; |
| } |
| |
| /** |
| * returns the current x-scale factor |
| */ |
| public float getScaleX() { |
| return mScaleX; |
| } |
| |
| /** |
| * returns the current y-scale factor |
| */ |
| public float getScaleY() { |
| return mScaleY; |
| } |
| |
| /** |
| * if the chart is fully zoomed out, return true |
| * |
| * @return |
| */ |
| public boolean isFullyZoomedOut() { |
| |
| // Log.i(LOG_TAG, "MinScaleX: " + mMinScaleX + ", ScaleX: " + mScaleX); |
| |
| if (mScaleX <= mMinScaleX && mScaleY <= mMinScaleY) |
| return true; |
| else |
| return false; |
| } |
| |
| /** |
| * calcualtes the maximum x-scale value depending on the number of x-values, |
| * maximum scale is numberOfXvals / 2 |
| * |
| * @return |
| */ |
| public float getMaxScaleX() { |
| return mDeltaX / 2f; |
| } |
| |
| /** |
| * Returns the maximum y-scale factor. Default 10f |
| * |
| * @return |
| */ |
| public float getMaxScaleY() { |
| return mMaxScaleY; |
| } |
| |
| /** |
| * sets the maximum scale factor for the y-axis. Default 7f, min 1f, max 20f |
| * |
| * @param factor |
| */ |
| public void setMaxScaleY(float factor) { |
| |
| if (factor < 1f) |
| factor = 1f; |
| if (factor > 20f) |
| factor = 20f; |
| |
| mMaxScaleY = factor; |
| } |
| |
| /** |
| * returns the object representing all y-labels, this method can be used to |
| * acquire the YLabels object and modify it (e.g. change the position of the |
| * labels) |
| * |
| * @return |
| */ |
| public YLabels getYLabels() { |
| return mYLabels; |
| } |
| |
| /** |
| * returns the object representing all x-labels, this method can be used to |
| * acquire the XLabels object and modify it (e.g. change the position of the |
| * labels) |
| * |
| * @return |
| */ |
| public XLabels getXLabels() { |
| return mXLabels; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * returns the filtered ChartData object depending on approximator settings, |
| * current scale level and x- and y-axis ratio |
| * |
| * @return |
| */ |
| private ChartData getFilteredData() { |
| // |
| // float deltaRatio = mDeltaY / mDeltaX; |
| // float scaleRatio = mScaleY / mScaleX; |
| // |
| // // set the determined ratios |
| // mApproximator.setRatios(deltaRatio, scaleRatio); |
| // |
| // // Log.i("Approximator", "DeltaRatio: " + deltaRatio + |
| // ", ScaleRatio: " |
| // // + scaleRatio); |
| // |
| // ArrayList<DataSet> dataSets = new ArrayList<DataSet>(); |
| // |
| // for (int j = 0; j < mOriginalData.getDataSetCount(); j++) { |
| // |
| // DataSet old = mOriginalData.getDataSetByIndex(j); |
| // |
| // // do the filtering |
| // ArrayList<Entry> approximated = mApproximator.filter(old.getYVals()); |
| // |
| // DataSet set = new DataSet(approximated, old.getLabel()); |
| // dataSets.add(set); |
| // } |
| // |
| // ChartData d = new ChartData(mOriginalData.getXVals(), dataSets); |
| // return d; |
| |
| return null; |
| } |
| |
| @Override |
| public void setPaint(Paint p, int which) { |
| super.setPaint(p, which); |
| |
| switch (which) { |
| case PAINT_GRID: |
| mGridPaint = p; |
| break; |
| case PAINT_GRID_BACKGROUND: |
| mGridBackgroundPaint = p; |
| break; |
| case PAINT_BORDER: |
| mBorderPaint = p; |
| break; |
| case PAINT_XLABEL: |
| mXLabelPaint = p; |
| break; |
| case PAINT_YLABEL: |
| mYLabelPaint = p; |
| break; |
| case PAINT_LIMIT_LINE: |
| mLimitLinePaint = p; |
| break; |
| } |
| } |
| |
| @Override |
| public Paint getPaint(int which) { |
| Paint p = super.getPaint(which); |
| if (p != null) |
| return p; |
| |
| switch (which) { |
| case PAINT_GRID: |
| return mGridPaint; |
| case PAINT_GRID_BACKGROUND: |
| return mGridBackgroundPaint; |
| case PAINT_BORDER: |
| return mBorderPaint; |
| case PAINT_XLABEL: |
| return mXLabelPaint; |
| case PAINT_YLABEL: |
| return mYLabelPaint; |
| case PAINT_LIMIT_LINE: |
| return mLimitLinePaint; |
| } |
| |
| return null; |
| } |
| } |